mirror of
https://github.com/GMWalletApp/epusdt.git
synced 2026-07-07 10:16:15 +00:00
Compare commits
6 Commits
v1.0.7
..
b4d1a4d8d4
| Author | SHA1 | Date | |
|---|---|---|---|
| b4d1a4d8d4 | |||
| 8216c91739 | |||
| de96a5d3e7 | |||
| 79ca1730cd | |||
| ad7679b7fd | |||
| 0215b2787e |
+2
-1
@@ -45,6 +45,7 @@ Deploy it privately, integrate with the HTTP API, and start receiving crypto pay
|
||||
| **Solana** | USDT, USDC |
|
||||
| **BEP20** (BSC) | USDT, USDC, BNB |
|
||||
| **Polygon** | USDT, USDC |
|
||||
| **Aptos** | USDC, USDT |
|
||||
| **More** | Expanding continuously |
|
||||
|
||||
> Actual support depends on the [latest release](https://github.com/GMWalletApp/epusdt/releases) and the [official docs](https://epusdt.com).
|
||||
@@ -68,7 +69,7 @@ No matter what kind of platform you operate, Epusdt can fit into existing paymen
|
||||
|
||||
## Core Features
|
||||
|
||||
- **Multi-chain and multi-token** — TRC20, ERC20, BEP20, Polygon, and more
|
||||
- **Multi-chain and multi-token** — TRC20, ERC20, BEP20, Polygon, Aptos, and more
|
||||
- **Self-hosted** — full control of funds and wallet infrastructure
|
||||
- **Low-dependency runtime** — a single binary can run the service; low-traffic setups do not require MySQL + Redis
|
||||
- **Cross-platform** — supports x86 / ARM on Windows / Linux / Mac
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
| **Solana** | USDT、USDC |
|
||||
| **BEP20** (BSC) | USDT、USDC、BNB |
|
||||
| **Polygon** | USDT、USDC |
|
||||
| **Aptos** | USDC、USDT |
|
||||
| **更多** | 持续扩展中… |
|
||||
|
||||
> 具体支持的链与代币以 [最新版本](https://github.com/GMWalletApp/epusdt/releases) 及 [官方文档](https://epusdt.com) 为准。
|
||||
@@ -74,7 +75,7 @@ Epusdt 已完成第三方安全审计。
|
||||
|
||||
## 核心特性
|
||||
|
||||
- **多链多币种** — 支持 TRC20、ERC20、BEP20、Polygon 等主流网络
|
||||
- **多链多币种** — 支持 TRC20、ERC20、BEP20、Polygon、Aptos 等主流网络
|
||||
- **私有化部署** — 资金完全自主掌控
|
||||
- **零依赖运行** — 单个二进制即可启动,低并发场景无需 MySQL + Redis
|
||||
- **跨平台** — 支持 x86 / ARM 架构的 Windows / Linux / Mac
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
.DS_Store
|
||||
dist/
|
||||
*.log
|
||||
bin/
|
||||
@@ -0,0 +1,41 @@
|
||||
APP := epusdt
|
||||
TARGET_DIR := bin
|
||||
BUILD_TAG ?= 0.0.0-dev
|
||||
VERSION_PKG := github.com/GMWalletApp/epusdt/config
|
||||
LDFLAGS := -s -w -X $(VERSION_PKG).BuildVersion=$(BUILD_TAG)
|
||||
|
||||
.PHONY: build release build-all swagger \
|
||||
linux-amd64 linux-arm64 linux-armv7 \
|
||||
darwin-amd64 darwin-arm64 \
|
||||
windows-amd64 windows-arm64
|
||||
|
||||
build:
|
||||
go build --trimpath -ldflags="$(LDFLAGS)" -o $(TARGET_DIR)/$(APP) .
|
||||
|
||||
release: build
|
||||
|
||||
linux-amd64:
|
||||
GOOS=linux GOARCH=amd64 go build --trimpath -ldflags="$(LDFLAGS)" -o $(TARGET_DIR)/$(APP)-linux-amd64 .
|
||||
|
||||
linux-arm64:
|
||||
GOOS=linux GOARCH=arm64 go build --trimpath -ldflags="$(LDFLAGS)" -o $(TARGET_DIR)/$(APP)-linux-arm64 .
|
||||
|
||||
linux-armv7:
|
||||
GOOS=linux GOARCH=arm GOARM=7 go build --trimpath -ldflags="$(LDFLAGS)" -o $(TARGET_DIR)/$(APP)-linux-armv7 .
|
||||
|
||||
darwin-amd64:
|
||||
GOOS=darwin GOARCH=amd64 go build --trimpath -ldflags="$(LDFLAGS)" -o $(TARGET_DIR)/$(APP)-darwin-amd64 .
|
||||
|
||||
darwin-arm64:
|
||||
GOOS=darwin GOARCH=arm64 go build --trimpath -ldflags="$(LDFLAGS)" -o $(TARGET_DIR)/$(APP)-darwin-arm64 .
|
||||
|
||||
windows-amd64:
|
||||
GOOS=windows GOARCH=amd64 go build --trimpath -ldflags="$(LDFLAGS)" -o $(TARGET_DIR)/$(APP)-windows-amd64.exe .
|
||||
|
||||
windows-arm64:
|
||||
GOOS=windows GOARCH=arm64 go build --trimpath -ldflags="$(LDFLAGS)" -o $(TARGET_DIR)/$(APP)-windows-arm64.exe .
|
||||
|
||||
build-all: linux-amd64 linux-arm64 linux-armv7 darwin-amd64 darwin-arm64 windows-amd64 windows-arm64
|
||||
|
||||
swagger:
|
||||
swag init --outputTypes json,yaml --parseInternal --parseDependency -o docs
|
||||
@@ -40,7 +40,7 @@ func (c *BaseAdminController) ListChains(ctx echo.Context) error {
|
||||
// @Security AdminJWT
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param network path string true "Network name (e.g. ton, tron, ethereum, solana)"
|
||||
// @Param network path string true "Network name (e.g. ton, tron, ethereum, solana, aptos)"
|
||||
// @Param request body admin.UpdateChainRequest true "Chain settings"
|
||||
// @Success 200 {object} response.ApiResponse
|
||||
// @Failure 400 {object} response.ApiResponse
|
||||
|
||||
@@ -14,6 +14,8 @@ import (
|
||||
type CreateChainTokenRequest struct {
|
||||
Network string `json:"network" validate:"required" example:"tron"`
|
||||
Symbol string `json:"symbol" validate:"required|maxLen:32" example:"USDT"`
|
||||
// Contract address or chain-native asset identifier. For Aptos this is
|
||||
// the Move asset id (coin type, metadata address, or object type).
|
||||
ContractAddress string `json:"contract_address" example:"TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"`
|
||||
Decimals int `json:"decimals" example:"6"`
|
||||
Enabled *bool `json:"enabled" example:"true"`
|
||||
@@ -22,6 +24,8 @@ type CreateChainTokenRequest struct {
|
||||
|
||||
// UpdateChainTokenRequest is the payload for updating a chain token.
|
||||
type UpdateChainTokenRequest struct {
|
||||
// Contract address or chain-native asset identifier. For Aptos this is
|
||||
// the Move asset id (coin type, metadata address, or object type).
|
||||
ContractAddress *string `json:"contract_address" example:"TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"`
|
||||
Decimals *int `json:"decimals" example:"6"`
|
||||
Enabled *bool `json:"enabled" example:"true"`
|
||||
@@ -33,6 +37,17 @@ type ChangeChainTokenStatusRequest struct {
|
||||
Enabled bool `json:"enabled" example:"true"`
|
||||
}
|
||||
|
||||
func validateChainTokenPaymentConfig(row *mdb.ChainToken) error {
|
||||
if row == nil {
|
||||
return nil
|
||||
}
|
||||
contractAddress := strings.TrimSpace(row.ContractAddress)
|
||||
if row.Enabled && row.Network == mdb.NetworkAptos && contractAddress == "" {
|
||||
return constant.ParamsMarshalErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListChainTokens returns rows, optionally filtered by network.
|
||||
// @Summary List chain tokens
|
||||
// @Description Returns chain tokens, optionally filtered by network
|
||||
@@ -54,7 +69,7 @@ func (c *BaseAdminController) ListChainTokens(ctx echo.Context) error {
|
||||
|
||||
// CreateChainToken inserts a new row. Default decimals=6 when 0 passed.
|
||||
// @Summary Create chain token
|
||||
// @Description Create a new chain token entry
|
||||
// @Description Create a new chain token entry. For Aptos, contract_address stores the Move asset id rather than an EVM contract address.
|
||||
// @Tags Admin Chain Tokens
|
||||
// @Security AdminJWT
|
||||
// @Accept json
|
||||
@@ -87,6 +102,9 @@ func (c *BaseAdminController) CreateChainToken(ctx echo.Context) error {
|
||||
Enabled: enabled,
|
||||
MinAmount: req.MinAmount,
|
||||
}
|
||||
if err := validateChainTokenPaymentConfig(row); err != nil {
|
||||
return c.FailJson(ctx, err)
|
||||
}
|
||||
if err := data.CreateChainToken(row); err != nil {
|
||||
return c.FailJson(ctx, err)
|
||||
}
|
||||
@@ -95,7 +113,7 @@ func (c *BaseAdminController) CreateChainToken(ctx echo.Context) error {
|
||||
|
||||
// UpdateChainToken patches mutable columns.
|
||||
// @Summary Update chain token
|
||||
// @Description Patch chain token fields
|
||||
// @Description Patch chain token fields. For Aptos, contract_address stores the Move asset id rather than an EVM contract address.
|
||||
// @Tags Admin Chain Tokens
|
||||
// @Security AdminJWT
|
||||
// @Accept json
|
||||
@@ -115,18 +133,35 @@ func (c *BaseAdminController) UpdateChainToken(ctx echo.Context) error {
|
||||
return c.FailJson(ctx, constant.ParamsMarshalErr)
|
||||
}
|
||||
fields := map[string]interface{}{}
|
||||
existing, err := data.GetChainTokenByID(id)
|
||||
if err != nil {
|
||||
return c.FailJson(ctx, err)
|
||||
}
|
||||
if existing == nil {
|
||||
existing = &mdb.ChainToken{}
|
||||
}
|
||||
next := *existing
|
||||
if req.ContractAddress != nil {
|
||||
fields["contract_address"] = *req.ContractAddress
|
||||
next.ContractAddress = strings.TrimSpace(*req.ContractAddress)
|
||||
fields["contract_address"] = next.ContractAddress
|
||||
}
|
||||
if req.Decimals != nil {
|
||||
next.Decimals = *req.Decimals
|
||||
fields["decimals"] = *req.Decimals
|
||||
}
|
||||
if req.Enabled != nil {
|
||||
next.Enabled = *req.Enabled
|
||||
fields["enabled"] = *req.Enabled
|
||||
}
|
||||
if req.MinAmount != nil {
|
||||
next.MinAmount = *req.MinAmount
|
||||
fields["min_amount"] = *req.MinAmount
|
||||
}
|
||||
if next.ID > 0 {
|
||||
if err = validateChainTokenPaymentConfig(&next); err != nil {
|
||||
return c.FailJson(ctx, err)
|
||||
}
|
||||
}
|
||||
if err := data.UpdateChainTokenFields(id, fields); err != nil {
|
||||
return c.FailJson(ctx, err)
|
||||
}
|
||||
@@ -154,6 +189,17 @@ func (c *BaseAdminController) ChangeChainTokenStatus(ctx echo.Context) error {
|
||||
if err := ctx.Bind(req); err != nil {
|
||||
return c.FailJson(ctx, constant.ParamsMarshalErr)
|
||||
}
|
||||
row, err := data.GetChainTokenByID(id)
|
||||
if err != nil {
|
||||
return c.FailJson(ctx, err)
|
||||
}
|
||||
if row != nil && row.ID > 0 {
|
||||
next := *row
|
||||
next.Enabled = req.Enabled
|
||||
if err = validateChainTokenPaymentConfig(&next); err != nil {
|
||||
return c.FailJson(ctx, err)
|
||||
}
|
||||
}
|
||||
if err := data.UpdateChainTokenFields(id, map[string]interface{}{"enabled": req.Enabled}); err != nil {
|
||||
return c.FailJson(ctx, err)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/GMWalletApp/epusdt/model/mdb"
|
||||
"github.com/GMWalletApp/epusdt/util/constant"
|
||||
)
|
||||
|
||||
func TestValidateChainTokenPaymentConfigRejectsEnabledAptosTokenWithoutAssetID(t *testing.T) {
|
||||
for _, row := range []mdb.ChainToken{
|
||||
{Network: mdb.NetworkAptos, Symbol: "USDT", Enabled: true},
|
||||
} {
|
||||
if err := validateChainTokenPaymentConfig(&row); err != constant.ParamsMarshalErr {
|
||||
t.Fatalf("validateChainTokenPaymentConfig(%+v) = %v, want %v", row, err, constant.ParamsMarshalErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateChainTokenPaymentConfigAllowsDisabledAptosTokenWithoutAssetID(t *testing.T) {
|
||||
row := mdb.ChainToken{Network: mdb.NetworkAptos, Symbol: "USDT", Enabled: false}
|
||||
if err := validateChainTokenPaymentConfig(&row); err != nil {
|
||||
t.Fatalf("validateChainTokenPaymentConfig(%+v) = %v, want nil", row, err)
|
||||
}
|
||||
}
|
||||
@@ -141,6 +141,8 @@ func (c *BaseAdminController) CloseOrder(ctx echo.Context) error {
|
||||
// @Summary Mark order paid
|
||||
// @Description Manually mark a waiting order as paid (operator补单)
|
||||
// @Description TON accepts canonical ton:<receive_raw>:<lt>:<hash>, lt:hash, or a unique recent hash-only reference for the order receive address.
|
||||
// @Description Aptos accepts a transaction hash.
|
||||
// @Description Aptos automatic scanning polls fullnode ledger-version transaction ranges with a runtime cursor.
|
||||
// @Tags Admin Orders
|
||||
// @Security AdminJWT
|
||||
// @Accept json
|
||||
|
||||
@@ -65,6 +65,7 @@ func (c *BaseCommController) CreateTransaction(ctx echo.Context) (err error) {
|
||||
// @Description Switch to a different payment target. A status=4 placeholder is completed in place and returns the same parent trade_id with is_selected=false; an already concrete status=1 parent creates or returns the only sub-order when switching to a different target.
|
||||
// @Description Normal values such as ton/tron/solana/ethereum select on-chain payment; the special value okpay selects OkPay hosted payment.
|
||||
// @Description For status=4 placeholders from GMPay or EPay submit.php, both on-chain targets and okpay complete the parent in place without creating a child order. Sub-orders cannot be switched again.
|
||||
// @Description For EPay orders with a merchant return_url, the returned redirect_url is the internal /pay/return/{trade_id} hop rather than the raw merchant return_url.
|
||||
// @Tags Payment
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package comm
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/GMWalletApp/epusdt/model/request"
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
// @Summary Checkout counter page
|
||||
// @Description Return checkout initialization data when the order exists. This endpoint only confirms order existence and returns base order data; call /pay/check-status/{trade_id} for the current order status (1=waiting payment, 2=paid, 3=expired, 4=waiting token/network selection).
|
||||
// @Description When status=4, actual_amount is 0 and token/network/receive_address are empty; this state is produced by GMPay placeholders or EPay submit.php when no token/network request values or database defaults exist. The cashier should guide the payer to choose an on-chain token/network or OkPay and then call /pay/switch-network.
|
||||
// @Description For EPay orders with a merchant return_url, the response redirect_url is rewritten to the internal /pay/return/{trade_id} hop; the database still stores the merchant's raw return_url.
|
||||
// @Tags Payment
|
||||
// @Produce json
|
||||
// @Param trade_id path string true "Trade ID"
|
||||
@@ -30,6 +32,29 @@ func (c *BaseCommController) CheckoutCounter(ctx echo.Context) (err error) {
|
||||
return c.SucJson(ctx, resp)
|
||||
}
|
||||
|
||||
// ReturnToMerchant performs the browser-facing EPay success return hop.
|
||||
// Non-EPay or not-yet-paid orders are sent back to the checkout counter.
|
||||
// @Summary Return to merchant (EPAY compat)
|
||||
// @Description Browser-facing success return hop for EPay orders. Paid EPay orders are redirected to the merchant return_url with signed legacy EPay query params. Orders that are not EPay or not yet paid are redirected back to the checkout counter.
|
||||
// @Description This route also returns explicit business errors when the merchant return_url is missing, the order API key is unavailable, or EPay signature construction fails.
|
||||
// @Tags Payment
|
||||
// @Produce html
|
||||
// @Param trade_id path string true "Trade ID"
|
||||
// @Success 302 "Redirect to merchant return_url or checkout counter"
|
||||
// @Failure 400 {object} response.ApiResponse "Stable errno in status_code: 10008 order not found, 10044 invalid order redirect url, 10045 order api key unavailable, 10046 failed to build epay return signature"
|
||||
// @Router /pay/return/{trade_id} [get]
|
||||
func (c *BaseCommController) ReturnToMerchant(ctx echo.Context) error {
|
||||
tradeID := ctx.Param("trade_id")
|
||||
redirect, err := service.ResolveEPayReturnRedirect(tradeID)
|
||||
if err != nil {
|
||||
return c.FailJson(ctx, err)
|
||||
}
|
||||
if redirect.IsMerchantRedirect {
|
||||
ctx.Response().Header().Set("Cache-Control", "no-store")
|
||||
}
|
||||
return ctx.Redirect(http.StatusFound, redirect.TargetURL)
|
||||
}
|
||||
|
||||
// CheckStatus 支付状态检测
|
||||
// @Summary Check payment status
|
||||
// @Description Return the current order status by trade ID. Status: 1=waiting payment, 2=paid, 3=expired, 4=waiting token/network selection.
|
||||
@@ -56,6 +81,8 @@ func (c *BaseCommController) CheckStatus(ctx echo.Context) (err error) {
|
||||
// @Summary Submit transaction hash
|
||||
// @Description Submit an on-chain transaction hash from the cashier. The hash is verified against the order and, when valid, the same manual payment processing path used by admin mark-paid is executed. OkPay/provider orders are not supported.
|
||||
// @Description TON accepts canonical ton:<receive_raw>:<lt>:<hash>, lt:hash, or a unique recent hash-only reference for the order receive address.
|
||||
// @Description Aptos accepts a transaction hash.
|
||||
// @Description Aptos automatic scanning polls fullnode ledger-version transaction ranges with a runtime cursor.
|
||||
// @Tags Payment
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/GMWalletApp/epusdt/model/data"
|
||||
"github.com/GMWalletApp/epusdt/model/mdb"
|
||||
"github.com/GMWalletApp/epusdt/model/response"
|
||||
"github.com/GMWalletApp/epusdt/model/service"
|
||||
"github.com/GMWalletApp/epusdt/util/constant"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
@@ -44,6 +45,9 @@ func buildSupportedAssets() ([]response.NetworkTokenSupport, error) {
|
||||
}
|
||||
symbols := make([]string, 0, len(tokens))
|
||||
for _, t := range tokens {
|
||||
if !service.ChainTokenReadyForPayment(t) {
|
||||
continue
|
||||
}
|
||||
sym := strings.ToUpper(strings.TrimSpace(t.Symbol))
|
||||
if sym == "" {
|
||||
continue
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package comm
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/GMWalletApp/epusdt/internal/testutil"
|
||||
"github.com/GMWalletApp/epusdt/model/dao"
|
||||
"github.com/GMWalletApp/epusdt/model/data"
|
||||
"github.com/GMWalletApp/epusdt/model/mdb"
|
||||
)
|
||||
|
||||
func TestBuildSupportedAssetsIncludesConfiguredAptosTokens(t *testing.T) {
|
||||
cleanup := testutil.SetupTestDatabases(t)
|
||||
defer cleanup()
|
||||
|
||||
if err := dao.Mdb.Create(&mdb.Chain{Network: mdb.NetworkAptos, Enabled: true, DisplayName: "Aptos"}).Error; err != nil {
|
||||
t.Fatalf("create aptos chain: %v", err)
|
||||
}
|
||||
if _, err := data.AddWalletAddressWithNetwork(mdb.NetworkAptos, "0x1"); err != nil {
|
||||
t.Fatalf("add aptos wallet: %v", err)
|
||||
}
|
||||
for _, row := range []mdb.ChainToken{
|
||||
{Network: mdb.NetworkAptos, Symbol: "USDT", ContractAddress: "0x357b0b74bc833e95a115ad22604854d6b0fca151cecd94111770e5d6ffc9dc2b", Decimals: 6, Enabled: true},
|
||||
{Network: mdb.NetworkAptos, Symbol: "MOVEUSD", ContractAddress: "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", Decimals: 6, Enabled: true},
|
||||
{Network: mdb.NetworkAptos, Symbol: "USDC", ContractAddress: "", Decimals: 6, Enabled: true},
|
||||
} {
|
||||
if err := dao.Mdb.Create(&row).Error; err != nil {
|
||||
t.Fatalf("create token %s: %v", row.Symbol, err)
|
||||
}
|
||||
}
|
||||
|
||||
supports, err := buildSupportedAssets()
|
||||
if err != nil {
|
||||
t.Fatalf("buildSupportedAssets: %v", err)
|
||||
}
|
||||
if len(supports) != 1 {
|
||||
t.Fatalf("supported assets len = %d, want 1: %#v", len(supports), supports)
|
||||
}
|
||||
if supports[0].Network != mdb.NetworkAptos {
|
||||
t.Fatalf("network = %q, want %q", supports[0].Network, mdb.NetworkAptos)
|
||||
}
|
||||
want := []string{"MOVEUSD", "USDT"}
|
||||
if len(supports[0].Tokens) != len(want) {
|
||||
t.Fatalf("tokens = %#v, want %#v", supports[0].Tokens, want)
|
||||
}
|
||||
for i := range want {
|
||||
if supports[0].Tokens[i] != want[i] {
|
||||
t.Fatalf("tokens = %#v, want %#v", supports[0].Tokens, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,6 +65,7 @@ func seedChains() {
|
||||
{Network: mdb.NetworkPolygon, DisplayName: "Polygon", Enabled: true, MinConfirmations: 3, ScanIntervalSec: 5},
|
||||
{Network: mdb.NetworkPlasma, DisplayName: "Plasma", Enabled: true, MinConfirmations: 1, ScanIntervalSec: 5},
|
||||
{Network: mdb.NetworkTon, DisplayName: "TON", Enabled: true, MinConfirmations: 1, ScanIntervalSec: 5},
|
||||
{Network: mdb.NetworkAptos, DisplayName: "Aptos", Enabled: true, MinConfirmations: 1, ScanIntervalSec: 5},
|
||||
}
|
||||
if err := Mdb.Clauses(clause.OnConflict{DoNothing: true}).Create(&defaults).Error; err != nil {
|
||||
color.Red.Printf("[store_db] seed chains err=%s\n", err)
|
||||
@@ -102,6 +103,8 @@ func seedChainTokens() {
|
||||
// TON
|
||||
{Network: mdb.NetworkTon, Symbol: "TON", ContractAddress: "", Decimals: 9, Enabled: true},
|
||||
{Network: mdb.NetworkTon, Symbol: "USDT", ContractAddress: "0:b113a994b5024a16719f69139328eb759596c38a25f59028b146fecdc3621dfe", Decimals: 6, Enabled: true},
|
||||
{Network: mdb.NetworkAptos, Symbol: "USDC", ContractAddress: "0xbae207659db88bea0cbead6da0ed00aac12edcdda169e591cd41c94180b46f3b", Decimals: 6, Enabled: true},
|
||||
{Network: mdb.NetworkAptos, Symbol: "USDT", ContractAddress: "0x357b0b74bc833e95a115ad22604854d6b0fca151cecd94111770e5d6ffc9dc2b", Decimals: 6, Enabled: true},
|
||||
}
|
||||
if err := Mdb.Clauses(clause.OnConflict{DoNothing: true}).Create(&defaults).Error; err != nil {
|
||||
color.Red.Printf("[store_db] seed chain_tokens err=%s\n", err)
|
||||
@@ -138,6 +141,7 @@ func defaultRpcNodes() []mdb.RpcNode {
|
||||
{Network: mdb.NetworkPolygon, Url: "wss://polygon-bor-rpc.publicnode.com", Type: mdb.RpcNodeTypeWs, Weight: 1, Enabled: true, Purpose: mdb.RpcNodePurposeGeneral, Status: mdb.RpcNodeStatusUnknown},
|
||||
{Network: mdb.NetworkPlasma, Url: "wss://rpc.plasma.to", Type: mdb.RpcNodeTypeWs, Weight: 1, Enabled: true, Purpose: mdb.RpcNodePurposeGeneral, Status: mdb.RpcNodeStatusUnknown},
|
||||
{Network: mdb.NetworkTon, Url: "https://ton-blockchain.github.io/global.config.json", Type: mdb.RpcNodeTypeLite, Weight: 1, Enabled: true, Purpose: mdb.RpcNodePurposeGeneral, Status: mdb.RpcNodeStatusUnknown},
|
||||
{Network: mdb.NetworkAptos, Url: "https://aptos-rest.publicnode.com/", Type: mdb.RpcNodeTypeHttp, Weight: 1, Enabled: true, Purpose: mdb.RpcNodePurposeGeneral, Status: mdb.RpcNodeStatusUnknown},
|
||||
{Network: mdb.NetworkEthereum, Url: "https://rpc.epusdt.com/ethereum", Type: mdb.RpcNodeTypeHttp, Weight: 1, Enabled: true, Purpose: mdb.RpcNodePurposeManualVerify, Status: mdb.RpcNodeStatusUnknown},
|
||||
{Network: mdb.NetworkBsc, Url: "https://rpc.epusdt.com/binance", Type: mdb.RpcNodeTypeHttp, Weight: 1, Enabled: true, Purpose: mdb.RpcNodePurposeManualVerify, Status: mdb.RpcNodeStatusUnknown},
|
||||
{Network: mdb.NetworkPolygon, Url: "https://rpc.epusdt.com/polygon", Type: mdb.RpcNodeTypeHttp, Weight: 1, Enabled: true, Purpose: mdb.RpcNodePurposeManualVerify, Status: mdb.RpcNodeStatusUnknown},
|
||||
|
||||
@@ -2,6 +2,7 @@ package dao
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/GMWalletApp/epusdt/model/mdb"
|
||||
@@ -74,6 +75,80 @@ func TestDefaultRpcNodesIncludesTonLiteGeneralNode(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultRpcNodesIncludesAptosPublicNode(t *testing.T) {
|
||||
var got *mdb.RpcNode
|
||||
nodes := defaultRpcNodes()
|
||||
for i := range nodes {
|
||||
node := nodes[i]
|
||||
if node.Network == mdb.NetworkAptos {
|
||||
got = &node
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if got == nil {
|
||||
t.Fatal("missing Aptos seed rpc node")
|
||||
}
|
||||
if got.Url != "https://aptos-rest.publicnode.com/" {
|
||||
t.Fatalf("Aptos seed url = %q", got.Url)
|
||||
}
|
||||
if got.Type != mdb.RpcNodeTypeHttp {
|
||||
t.Fatalf("Aptos seed type = %q, want %q", got.Type, mdb.RpcNodeTypeHttp)
|
||||
}
|
||||
if got.Purpose != mdb.RpcNodePurposeGeneral {
|
||||
t.Fatalf("Aptos seed purpose = %q, want %q", got.Purpose, mdb.RpcNodePurposeGeneral)
|
||||
}
|
||||
if !got.Enabled {
|
||||
t.Fatal("Aptos seed enabled = false, want true")
|
||||
}
|
||||
if got.Status != mdb.RpcNodeStatusUnknown {
|
||||
t.Fatalf("Aptos seed status = %q, want %q", got.Status, mdb.RpcNodeStatusUnknown)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSeedChainsIncludesAptos(t *testing.T) {
|
||||
db := setupSeedTableTestDB(t, &mdb.Chain{})
|
||||
Mdb = db
|
||||
|
||||
seedChains()
|
||||
|
||||
var row mdb.Chain
|
||||
if err := Mdb.Where("network = ?", mdb.NetworkAptos).Take(&row).Error; err != nil {
|
||||
t.Fatalf("load Aptos chain seed: %v", err)
|
||||
}
|
||||
if !row.Enabled {
|
||||
t.Fatal("Aptos chain enabled = false, want true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSeedChainTokensIncludesAptosAssets(t *testing.T) {
|
||||
db := setupSeedTableTestDB(t, &mdb.ChainToken{})
|
||||
Mdb = db
|
||||
|
||||
seedChainTokens()
|
||||
|
||||
wantEnabled := map[string]bool{
|
||||
mdb.NetworkAptos + "/USDC": true,
|
||||
mdb.NetworkAptos + "/USDT": true,
|
||||
}
|
||||
wantContract := map[string]string{
|
||||
mdb.NetworkAptos + "/USDT": "0x357b0b74bc833e95a115ad22604854d6b0fca151cecd94111770e5d6ffc9dc2b",
|
||||
}
|
||||
for key, enabled := range wantEnabled {
|
||||
parts := strings.Split(key, "/")
|
||||
var row mdb.ChainToken
|
||||
if err := Mdb.Where("network = ? AND symbol = ?", parts[0], parts[1]).Take(&row).Error; err != nil {
|
||||
t.Fatalf("load token seed %s: %v", key, err)
|
||||
}
|
||||
if row.Enabled != enabled {
|
||||
t.Fatalf("%s enabled = %v, want %v", key, row.Enabled, enabled)
|
||||
}
|
||||
if want, ok := wantContract[key]; ok && row.ContractAddress != want {
|
||||
t.Fatalf("%s contract_address = %q, want %q", key, row.ContractAddress, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSeedDefaultSettingsIncludesSystemLogLevel(t *testing.T) {
|
||||
db := setupSeedSettingsTestDB(t)
|
||||
Mdb = db
|
||||
@@ -194,3 +269,19 @@ func setupSeedSettingsTestDB(t *testing.T) *gorm.DB {
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
func setupSeedTableTestDB(t *testing.T, models ...interface{}) *gorm.DB {
|
||||
t.Helper()
|
||||
oldDB := Mdb
|
||||
t.Cleanup(func() {
|
||||
Mdb = oldDB
|
||||
})
|
||||
db, err := gorm.Open(sqlite.Open(filepath.Join(t.TempDir(), "seed-table.db")), &gorm.Config{})
|
||||
if err != nil {
|
||||
t.Fatalf("open sqlite: %v", err)
|
||||
}
|
||||
if err := db.AutoMigrate(models...); err != nil {
|
||||
t.Fatalf("migrate seed table: %v", err)
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
@@ -49,6 +49,11 @@ func normalizeLockAddress(network, address string) string {
|
||||
return raw
|
||||
}
|
||||
}
|
||||
if network == mdb.NetworkAptos {
|
||||
if normalized, err := addressutil.NormalizeMoveAddress(address); err == nil {
|
||||
return normalized
|
||||
}
|
||||
}
|
||||
return address
|
||||
}
|
||||
|
||||
|
||||
@@ -184,3 +184,19 @@ func TestTonTransactionLockAddressUsesRawKey(t *testing.T) {
|
||||
t.Fatalf("ton raw lock lookup = %q, want trade-ton", gotTradeID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAptosTransactionLockAddressUsesCanonicalKey(t *testing.T) {
|
||||
cleanup := testutil.SetupTestDatabases(t)
|
||||
defer cleanup()
|
||||
|
||||
if err := LockTransaction(mdb.NetworkAptos, "0xA", "USDT", "trade-aptos", 1.23, time.Hour); err != nil {
|
||||
t.Fatalf("lock aptos transaction: %v", err)
|
||||
}
|
||||
gotTradeID, err := GetTradeIdByWalletAddressAndAmountAndToken(mdb.NetworkAptos, "a", "USDT", 1.23)
|
||||
if err != nil {
|
||||
t.Fatalf("lookup aptos lock: %v", err)
|
||||
}
|
||||
if gotTradeID != "trade-aptos" {
|
||||
t.Fatalf("aptos lock lookup = %q, want trade-aptos", gotTradeID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,9 @@ func normalizeWalletAddressByNetworkE(network, address string) (string, error) {
|
||||
if network == mdb.NetworkTon {
|
||||
return addressutil.NormalizeTonAddress(address)
|
||||
}
|
||||
if network == mdb.NetworkAptos {
|
||||
return addressutil.NormalizeMoveAddress(address)
|
||||
}
|
||||
return normalizeWalletAddressByNetwork(network, address), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -103,3 +103,20 @@ func TestAddWalletAddressWithNetworkNormalizesTonAddressVariants(t *testing.T) {
|
||||
t.Fatalf("add equivalent ton wallet error = %v, want already exists", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddWalletAddressWithNetworkNormalizesMoveAddressVariants(t *testing.T) {
|
||||
cleanup := testutil.SetupTestDatabases(t)
|
||||
defer cleanup()
|
||||
|
||||
want := "0x000000000000000000000000000000000000000000000000000000000000000a"
|
||||
row, err := AddWalletAddressWithNetwork(mdb.NetworkAptos, " A ")
|
||||
if err != nil {
|
||||
t.Fatalf("add aptos wallet: %v", err)
|
||||
}
|
||||
if row.Address != want {
|
||||
t.Fatalf("stored Aptos address = %q, want %q", row.Address, want)
|
||||
}
|
||||
if _, err = AddWalletAddressWithNetwork(mdb.NetworkAptos, "0x0A"); err != constant.WalletAddressAlreadyExists {
|
||||
t.Fatalf("add equivalent aptos wallet error = %v, want already exists", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,11 @@ const (
|
||||
ChainTokenStatusDisable = 2
|
||||
)
|
||||
|
||||
// ChainToken describes a token to watch on a given chain. Replaces the
|
||||
// hardcoded USDT/USDC contract addresses in task/listen_eth.go. Scanners
|
||||
// load this table on startup (and refresh periodically) to learn which
|
||||
// contracts to subscribe to.
|
||||
// ChainToken describes a token to watch on a given chain. For EVM/TRON rows
|
||||
// ContractAddress is the token contract; for Solana it is the mint; for
|
||||
// TON it is the jetton master; for Aptos it is the Move asset identifier
|
||||
// (coin type, metadata address or object type). Native rows use an empty
|
||||
// ContractAddress.
|
||||
type ChainToken struct {
|
||||
Network string `gorm:"column:network;size:32;uniqueIndex:chain_tokens_network_symbol_uindex,priority:1" json:"network" example:"tron"`
|
||||
Symbol string `gorm:"column:symbol;size:32;uniqueIndex:chain_tokens_network_symbol_uindex,priority:2" json:"symbol" example:"USDT"`
|
||||
|
||||
@@ -13,6 +13,7 @@ const (
|
||||
NetworkPolygon = "polygon"
|
||||
NetworkPlasma = "plasma"
|
||||
NetworkTon = "ton"
|
||||
NetworkAptos = "aptos"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -7,7 +7,7 @@ type CreateTransactionRequest struct {
|
||||
OrderId string `json:"order_id" form:"order_id" validate:"required|maxLen:32" example:"ORD20260416001"`
|
||||
Currency string `json:"currency" form:"currency" validate:"required" example:"cny"` // 法币 如:cny
|
||||
Token string `json:"token" form:"token" example:"usdt"` // 币种 如:usdt、ton;可与 network 同时缺省创建占位订单
|
||||
Network string `json:"network" form:"network" example:"tron"` // 网络 如:ton、tron;可与 token 同时缺省创建占位订单
|
||||
Network string `json:"network" form:"network" example:"tron"` // 网络 如:ton、tron、aptos;可与 token 同时缺省创建占位订单
|
||||
Amount float64 `json:"amount" form:"amount" validate:"required|isFloat|gt:0.01" example:"100.00"`
|
||||
NotifyUrl string `json:"notify_url" form:"notify_url" validate:"required" example:"https://example.com/notify"`
|
||||
Signature string `json:"signature" form:"signature" validate:"required" example:"a1b2c3d4e5f6..."`
|
||||
@@ -58,7 +58,7 @@ func (r ManualPaymentRequest) Translates() map[string]string {
|
||||
type SwitchNetworkRequest struct {
|
||||
TradeId string `json:"trade_id" validate:"required" example:"3nQ9pL2xV7sK1mR8cT4yB_aZ"`
|
||||
Token string `json:"token" validate:"required" example:"USDT"`
|
||||
Network string `json:"network" validate:"required" example:"okpay,tron,solana,ethereum"`
|
||||
Network string `json:"network" validate:"required" example:"okpay,tron,solana,ethereum,aptos"`
|
||||
}
|
||||
|
||||
func (r SwitchNetworkRequest) Translates() map[string]string {
|
||||
|
||||
@@ -11,7 +11,7 @@ type CheckoutCounterResponse struct {
|
||||
Status int `json:"status" enums:"1,2,3,4" example:"1"` // 订单状态 1=等待支付 2=支付成功 3=已过期 4=等待选择支付网络/币种;status=4 时前端应引导选择链上 token/network 或 OkPay
|
||||
PaymentType string `json:"payment_type" enums:"gmpay,epay" example:"gmpay"` // 支付接入类型;底层 Epay/Gmpay 转为小写 epay/gmpay 返回
|
||||
ExpirationTime int64 `json:"expiration_time" example:"1713264600"` // 过期时间 时间戳
|
||||
RedirectUrl string `json:"redirect_url" example:"https://example.com/success"`
|
||||
RedirectUrl string `json:"redirect_url" example:"https://example.com/success"` // 非 EPay 时为商户原始回跳地址;EPay 时为内部中转地址 /pay/return/{trade_id}
|
||||
PaymentUrl string `json:"payment_url" example:"https://pay.example.com/checkout/3nQ9pL2xV7sK1mR8cT4yB_aZ"` // 支付链接;链上订单为空,OkPay 订单为第三方 payLink
|
||||
CreatedAt int64 `json:"created_at" example:"1713264000"` // 订单创建时间 时间戳
|
||||
IsSelected bool `json:"is_selected" example:"false"` // 是否已选择当前支付方式;status=4 占位订单和刚补全的占位父单为 false
|
||||
|
||||
@@ -0,0 +1,526 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/GMWalletApp/epusdt/model/data"
|
||||
"github.com/GMWalletApp/epusdt/model/mdb"
|
||||
addressutil "github.com/GMWalletApp/epusdt/util/address"
|
||||
"github.com/GMWalletApp/epusdt/util/log"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func AptosGetLedgerVersion() (int64, error) {
|
||||
body, err := aptosGet("/v1")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
latest := gjson.GetBytes(body, "ledger_version").Int()
|
||||
log.Sugar.Debugf("[APTOS] latest ledger version=%d", latest)
|
||||
return latest, nil
|
||||
}
|
||||
|
||||
func AptosGetTransactionByHash(txID string) ([]byte, error) {
|
||||
txID = normalizeAptosTxID(txID)
|
||||
log.Sugar.Infof("[APTOS] fetch transaction by hash tx=%s", txID)
|
||||
return aptosGet(fmt.Sprintf("/v1/transactions/by_hash/%s", txID))
|
||||
}
|
||||
|
||||
func AptosGetTransactions(start int64, limit int64) ([]byte, error) {
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
if limit <= 0 {
|
||||
limit = 1
|
||||
}
|
||||
log.Sugar.Debugf("[APTOS] fetch transactions start=%d limit=%d", start, limit)
|
||||
return aptosGet(fmt.Sprintf("/v1/transactions?start=%d&limit=%d", start, limit))
|
||||
}
|
||||
|
||||
func ParseAptosTransfers(body []byte, address string, tokens []mdb.ChainToken) ([]MoveObservedTransfer, error) {
|
||||
receive, err := addressutil.NormalizeMoveAddress(address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ParseAptosTransfersForWallets(body, map[string]struct{}{receive: {}}, tokens)
|
||||
}
|
||||
|
||||
func ParseAptosTransfersForWallets(body []byte, wallets map[string]struct{}, tokens []mdb.ChainToken) ([]MoveObservedTransfer, error) {
|
||||
root := gjson.ParseBytes(body)
|
||||
if !root.IsArray() {
|
||||
return nil, fmt.Errorf("unexpected Aptos transactions response")
|
||||
}
|
||||
var out []MoveObservedTransfer
|
||||
for _, tx := range root.Array() {
|
||||
if tx.Get("type").String() != "" && tx.Get("type").String() != "user_transaction" {
|
||||
continue
|
||||
}
|
||||
if !tx.Get("success").Bool() {
|
||||
continue
|
||||
}
|
||||
txID := strings.TrimSpace(tx.Get("hash").String())
|
||||
if txID == "" {
|
||||
continue
|
||||
}
|
||||
blockTimeMs := parseAptosTimestampMs(tx.Get("timestamp"))
|
||||
version := tx.Get("version").Int()
|
||||
txctx := buildAptosTransferContext(tx)
|
||||
events := buildAptosFungibleEvents(tx, txctx, tokens)
|
||||
deposits := make([]aptosFungibleEvent, 0)
|
||||
withdrawals := make(map[string]int)
|
||||
for _, event := range events {
|
||||
if event.token == nil {
|
||||
continue
|
||||
}
|
||||
switch event.action {
|
||||
case "withdraw":
|
||||
withdrawals[aptosTransferMatchKey(event.token.Symbol, event.rawAmount)]++
|
||||
case "deposit":
|
||||
deposits = append(deposits, event)
|
||||
}
|
||||
}
|
||||
usedDeposits := make([]bool, len(deposits))
|
||||
for i, event := range deposits {
|
||||
matchKey := aptosTransferMatchKey(event.token.Symbol, event.rawAmount)
|
||||
if withdrawals[matchKey] <= 0 {
|
||||
continue
|
||||
}
|
||||
if appendAptosObservedTransfer(&out, txID, version, blockTimeMs, event, wallets) {
|
||||
withdrawals[matchKey]--
|
||||
usedDeposits[i] = true
|
||||
} else {
|
||||
logAptosUnwatchedDeposit(txID, version, event, "direct")
|
||||
}
|
||||
}
|
||||
for i := 0; i < len(deposits); i++ {
|
||||
if usedDeposits[i] {
|
||||
continue
|
||||
}
|
||||
for j := i + 1; j < len(deposits); j++ {
|
||||
if usedDeposits[j] || !aptosDepositPairCanMatch(deposits[i], deposits[j]) {
|
||||
continue
|
||||
}
|
||||
sum := new(big.Int).Add(deposits[i].rawAmount, deposits[j].rawAmount)
|
||||
matchKey := aptosTransferMatchKey(deposits[i].token.Symbol, sum)
|
||||
if withdrawals[matchKey] <= 0 {
|
||||
continue
|
||||
}
|
||||
appendedFirst := appendAptosObservedTransfer(&out, txID, version, blockTimeMs, deposits[i], wallets)
|
||||
appendedSecond := appendAptosObservedTransfer(&out, txID, version, blockTimeMs, deposits[j], wallets)
|
||||
if !appendedFirst && !appendedSecond {
|
||||
logAptosUnwatchedDeposit(txID, version, deposits[i], "split")
|
||||
logAptosUnwatchedDeposit(txID, version, deposits[j], "split")
|
||||
continue
|
||||
}
|
||||
withdrawals[matchKey]--
|
||||
usedDeposits[i] = true
|
||||
usedDeposits[j] = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func appendAptosObservedTransfer(out *[]MoveObservedTransfer, txID string, version int64, blockTimeMs int64, event aptosFungibleEvent, wallets map[string]struct{}) bool {
|
||||
recipient := event.owner
|
||||
if !moveWalletWatched(recipient, wallets) {
|
||||
return false
|
||||
}
|
||||
*out = append(*out, MoveObservedTransfer{
|
||||
Network: mdb.NetworkAptos,
|
||||
ReceiveAddress: recipient,
|
||||
Token: NormalizeAptosPaymentSymbol(event.token.Symbol),
|
||||
RawAmount: event.rawAmount,
|
||||
Decimals: event.token.Decimals,
|
||||
MinAmount: event.token.MinAmount,
|
||||
Amount: rawToFloat(event.rawAmount, event.token.Decimals),
|
||||
TxID: normalizeAptosTxID(txID),
|
||||
BlockTimeMs: blockTimeMs,
|
||||
Version: version,
|
||||
TransferKey: moveAptosTransferKey(version, event.eventIndex, recipient, event.token.Symbol, event.rawAmount),
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
func logAptosUnwatchedDeposit(txID string, version int64, event aptosFungibleEvent, matchMode string) {
|
||||
log.Sugar.Debugf(
|
||||
"[APTOS] matched deposit skipped owner_not_watched mode=%s version=%d tx=%s token=%s amount=%.8f raw=%s owner=%s",
|
||||
matchMode,
|
||||
version,
|
||||
normalizeAptosTxID(txID),
|
||||
NormalizeAptosPaymentSymbol(event.token.Symbol),
|
||||
rawToFloat(event.rawAmount, event.token.Decimals),
|
||||
event.rawAmount.String(),
|
||||
event.owner,
|
||||
)
|
||||
}
|
||||
|
||||
func aptosDepositPairCanMatch(first, second aptosFungibleEvent) bool {
|
||||
if first.token == nil || second.token == nil || first.rawAmount == nil || second.rawAmount == nil {
|
||||
return false
|
||||
}
|
||||
if NormalizeAptosPaymentSymbol(first.token.Symbol) != NormalizeAptosPaymentSymbol(second.token.Symbol) {
|
||||
return false
|
||||
}
|
||||
return first.token.ContractAddress == second.token.ContractAddress
|
||||
}
|
||||
|
||||
type aptosFungibleEvent struct {
|
||||
eventIndex int
|
||||
action string
|
||||
owner string
|
||||
rawAmount *big.Int
|
||||
token *mdb.ChainToken
|
||||
}
|
||||
|
||||
func buildAptosFungibleEvents(tx gjson.Result, ctx aptosTransferContext, tokens []mdb.ChainToken) []aptosFungibleEvent {
|
||||
out := make([]aptosFungibleEvent, 0)
|
||||
for eventIndex, event := range tx.Get("events").Array() {
|
||||
action := aptosFungibleEventAction(event)
|
||||
if action == "" {
|
||||
continue
|
||||
}
|
||||
rawAmount, ok := parseSignedBigInt(aptosEventRawAmount(event, ctx))
|
||||
if !ok || rawAmount.Sign() <= 0 {
|
||||
continue
|
||||
}
|
||||
token := resolveAptosPaymentToken(aptosEventAssetID(event, ctx), tokens)
|
||||
if token == nil {
|
||||
continue
|
||||
}
|
||||
out = append(out, aptosFungibleEvent{
|
||||
eventIndex: eventIndex,
|
||||
action: action,
|
||||
owner: aptosEventRecipient(event, ctx),
|
||||
rawAmount: rawAmount,
|
||||
token: token,
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func aptosFungibleEventAction(event gjson.Result) string {
|
||||
typ := strings.ToLower(strings.TrimSpace(event.Get("type").String()))
|
||||
switch typ {
|
||||
case "0x1::fungible_asset::deposit":
|
||||
return "deposit"
|
||||
case "0x1::fungible_asset::withdraw":
|
||||
return "withdraw"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func aptosTransferMatchKey(symbol string, rawAmount *big.Int) string {
|
||||
raw := ""
|
||||
if rawAmount != nil {
|
||||
raw = rawAmount.String()
|
||||
}
|
||||
return NormalizeAptosPaymentSymbol(symbol) + ":" + raw
|
||||
}
|
||||
|
||||
func resolveAptosPaymentToken(assetID string, tokens []mdb.ChainToken) *mdb.ChainToken {
|
||||
return resolveMoveToken(mdb.NetworkAptos, assetID, tokens)
|
||||
}
|
||||
|
||||
func NormalizeAptosPaymentSymbol(symbol string) string {
|
||||
return strings.ToUpper(strings.TrimSpace(symbol))
|
||||
}
|
||||
|
||||
type aptosTransferContext struct {
|
||||
storeOwners map[string]string
|
||||
storeMetadata map[string]string
|
||||
}
|
||||
|
||||
func buildAptosTransferContext(tx gjson.Result) aptosTransferContext {
|
||||
ctx := aptosTransferContext{
|
||||
storeOwners: make(map[string]string),
|
||||
storeMetadata: make(map[string]string),
|
||||
}
|
||||
for _, change := range tx.Get("changes").Array() {
|
||||
if change.Get("type").String() != "write_resource" {
|
||||
continue
|
||||
}
|
||||
store, err := addressutil.NormalizeMoveAddress(change.Get("address").String())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
changeType := strings.TrimSpace(change.Get("data.type").String())
|
||||
switch changeType {
|
||||
case "0x1::fungible_asset::FungibleStore":
|
||||
if metadata := firstNonEmpty(
|
||||
change.Get("data.data.metadata.inner").String(),
|
||||
change.Get("data.data.metadata").String(),
|
||||
); metadata != "" {
|
||||
ctx.storeMetadata[store] = normalizeAptosObservedAssetID(metadata)
|
||||
}
|
||||
case "0x1::object::ObjectCore":
|
||||
if owner := firstMoveAddress(change.Get("data.data.owner").String()); owner != "" {
|
||||
ctx.storeOwners[store] = owner
|
||||
}
|
||||
}
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
func aptosEventRecipient(event gjson.Result, ctx aptosTransferContext) string {
|
||||
if store := firstMoveAddress(event.Get("data.store").String()); store != "" {
|
||||
if owner := ctx.storeOwners[store]; owner != "" {
|
||||
return owner
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func aptosEventRawAmount(event gjson.Result, ctx aptosTransferContext) string {
|
||||
return event.Get("data.amount").String()
|
||||
}
|
||||
|
||||
func aptosEventAssetID(event gjson.Result, ctx aptosTransferContext) string {
|
||||
if store := firstMoveAddress(event.Get("data.store").String()); store != "" {
|
||||
if metadata := ctx.storeMetadata[store]; metadata != "" {
|
||||
return metadata
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func normalizeAptosObservedAssetID(assetID string) string {
|
||||
return addressutil.NormalizeMoveAssetID(assetID)
|
||||
}
|
||||
|
||||
func parseAptosTimestampMs(value gjson.Result) int64 {
|
||||
raw := strings.TrimSpace(value.String())
|
||||
if raw == "" {
|
||||
return 0
|
||||
}
|
||||
n, err := strconv.ParseInt(raw, 10, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return n / 1000
|
||||
}
|
||||
|
||||
func normalizeAptosTxID(txID string) string {
|
||||
txID = strings.ToLower(strings.TrimSpace(txID))
|
||||
if strings.HasPrefix(txID, "0x") {
|
||||
return txID
|
||||
}
|
||||
return "0x" + txID
|
||||
}
|
||||
|
||||
func ValidateManualAptosPayment(order *mdb.Orders, txID string) (string, error) {
|
||||
txID = normalizeAptosTxID(txID)
|
||||
nodes, err := data.ListManualPaymentRpcCandidates(mdb.NetworkAptos, mdb.RpcNodeTypeHttp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(nodes) == 0 {
|
||||
return "", fmt.Errorf("no enabled %s %s RPC node configured in rpc_nodes", mdb.NetworkAptos, mdb.RpcNodeTypeHttp)
|
||||
}
|
||||
|
||||
var verifyErrors []string
|
||||
for _, node := range nodes {
|
||||
if strings.TrimSpace(node.Url) == "" {
|
||||
continue
|
||||
}
|
||||
if _, err = validateManualAptosPaymentWithNode(order, txID, node); err != nil {
|
||||
verifyErrors = append(verifyErrors, fmt.Sprintf("%s: %v", data.RpcNodeLogLabel(node), err))
|
||||
continue
|
||||
}
|
||||
return txID, nil
|
||||
}
|
||||
if len(verifyErrors) > 0 {
|
||||
return "", fmt.Errorf("manual Aptos verification failed: %s", strings.Join(verifyErrors, "; "))
|
||||
}
|
||||
return "", fmt.Errorf("no enabled %s %s RPC node configured in rpc_nodes", mdb.NetworkAptos, mdb.RpcNodeTypeHttp)
|
||||
}
|
||||
|
||||
func validateManualAptosPaymentWithNode(order *mdb.Orders, txID string, node mdb.RpcNode) (string, error) {
|
||||
txID = normalizeAptosTxID(txID)
|
||||
body, err := aptosGetWithNode(node, fmt.Sprintf("/v1/transactions/by_hash/%s", txID))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("fetch aptos transaction: %w", err)
|
||||
}
|
||||
if err = validateManualAptosTransactionWithConfirm(order, txID, body, func(version int64) error {
|
||||
return ensureAptosTransferConfirmedWithNode(version, node)
|
||||
}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return txID, nil
|
||||
}
|
||||
|
||||
func validateManualAptosTransaction(order *mdb.Orders, txID string, body []byte) error {
|
||||
return validateManualAptosTransactionWithConfirm(order, txID, body, EnsureAptosTransferConfirmed)
|
||||
}
|
||||
|
||||
func validateManualAptosTransactionWithConfirm(order *mdb.Orders, txID string, body []byte, confirm func(int64) error) error {
|
||||
tokens, err := data.ListEnabledChainTokensByNetwork(mdb.NetworkAptos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
transfers, err := ParseAptosTransfers([]byte("["+string(body)+"]"), order.ReceiveAddress, tokens)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
amountMismatch := false
|
||||
for _, transfer := range transfers {
|
||||
if !strings.EqualFold(transfer.TxID, txID) {
|
||||
continue
|
||||
}
|
||||
if err = confirm(transfer.Version); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = EnsureMoveTransferMatchesOrder(order, transfer); err == nil {
|
||||
return nil
|
||||
}
|
||||
if strings.Contains(err.Error(), "amount mismatch") {
|
||||
amountMismatch = true
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if amountMismatch {
|
||||
return fmt.Errorf("transaction amount mismatch")
|
||||
}
|
||||
return fmt.Errorf("matching aptos transfer to order address not found")
|
||||
}
|
||||
|
||||
func aptosGet(path string) (body []byte, err error) {
|
||||
network := mdb.NetworkAptos
|
||||
defer recordMoveRPCResult(network, &err)
|
||||
|
||||
tried := make([]uint64, 0, 3)
|
||||
var lastErr error
|
||||
for attempts := 0; attempts < 3; attempts++ {
|
||||
node, resolveErr := resolveAptosRpcNode(tried...)
|
||||
if resolveErr != nil {
|
||||
if lastErr != nil {
|
||||
return nil, lastErr
|
||||
}
|
||||
return nil, resolveErr
|
||||
}
|
||||
if len(tried) > 0 {
|
||||
log.Sugar.Warnf("[APTOS] trying alternate RPC node path=%s node=%s", path, data.RpcNodeLogLabel(*node))
|
||||
}
|
||||
|
||||
body, err = aptosGetWithNode(*node, path)
|
||||
if err == nil {
|
||||
data.RecordRpcNodeSuccess(node.ID)
|
||||
return body, nil
|
||||
}
|
||||
|
||||
lastErr = err
|
||||
failures, cooling := data.RecordRpcNodeFailure(node.ID)
|
||||
nodeLabel := data.RpcNodeLogLabel(*node)
|
||||
if cooling {
|
||||
log.Sugar.Warnf("[APTOS] RPC node reached fail threshold path=%s node=%s failures=%d/%d", path, nodeLabel, failures, data.RpcFailoverThreshold)
|
||||
} else {
|
||||
log.Sugar.Warnf("[APTOS] RPC node failed path=%s node=%s failures=%d/%d", path, nodeLabel, failures, data.RpcFailoverThreshold)
|
||||
}
|
||||
tried = append(tried, node.ID)
|
||||
}
|
||||
return nil, lastErr
|
||||
}
|
||||
|
||||
func resolveAptosRpcNode(excludeIDs ...uint64) (*mdb.RpcNode, error) {
|
||||
node, err := data.SelectGeneralRpcNode(mdb.NetworkAptos, mdb.RpcNodeTypeHttp, excludeIDs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if node == nil || node.ID == 0 {
|
||||
return nil, fmt.Errorf("no enabled %s %s RPC node configured in rpc_nodes", mdb.NetworkAptos, mdb.RpcNodeTypeHttp)
|
||||
}
|
||||
rpcURL := strings.TrimSpace(node.Url)
|
||||
if rpcURL == "" {
|
||||
return nil, fmt.Errorf("rpc_nodes id=%d has empty url", node.ID)
|
||||
}
|
||||
node.Url = rpcURL
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func aptosGetWithNode(node mdb.RpcNode, path string) (body []byte, err error) {
|
||||
endpoint, err := aptosJoinURL(node.Url, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := http.NewRequest(http.MethodGet, endpoint, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Accept", "application/json")
|
||||
resp, err := (&http.Client{Timeout: manualVerifyRequestTimeout}).Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, _ = io.ReadAll(resp.Body)
|
||||
if resp.StatusCode >= http.StatusBadRequest {
|
||||
return nil, fmt.Errorf("aptos rpc HTTP %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
|
||||
func aptosJoinURL(base, path string) (string, error) {
|
||||
base = strings.TrimSpace(base)
|
||||
if base == "" {
|
||||
return "", fmt.Errorf("empty aptos rpc url")
|
||||
}
|
||||
if strings.TrimSpace(path) == "" {
|
||||
return strings.TrimRight(base, "/"), nil
|
||||
}
|
||||
path = strings.TrimSpace(path)
|
||||
trimmedBase := strings.TrimRight(base, "/")
|
||||
lowerBase := strings.ToLower(trimmedBase)
|
||||
if strings.HasSuffix(lowerBase, "/v1") && strings.HasPrefix(path, "/v1") {
|
||||
path = strings.TrimPrefix(path, "/v1")
|
||||
if path == "" {
|
||||
path = "/"
|
||||
}
|
||||
}
|
||||
return trimmedBase + path, nil
|
||||
}
|
||||
|
||||
func EnsureAptosTransferConfirmed(version int64) error {
|
||||
if version <= 0 {
|
||||
return fmt.Errorf("aptos transaction version missing")
|
||||
}
|
||||
latest, err := AptosGetLedgerVersion()
|
||||
if err != nil {
|
||||
return fmt.Errorf("fetch aptos latest ledger version: %w", err)
|
||||
}
|
||||
return ensureAptosConfirmations(version, latest)
|
||||
}
|
||||
|
||||
func ensureAptosTransferConfirmedWithNode(version int64, node mdb.RpcNode) error {
|
||||
if version <= 0 {
|
||||
return fmt.Errorf("aptos transaction version missing")
|
||||
}
|
||||
body, err := aptosGetWithNode(node, "/v1")
|
||||
if err != nil {
|
||||
return fmt.Errorf("fetch aptos latest ledger version: %w", err)
|
||||
}
|
||||
latest := gjson.GetBytes(body, "ledger_version").Int()
|
||||
return ensureAptosConfirmations(version, latest)
|
||||
}
|
||||
|
||||
func ensureAptosConfirmations(version, latest int64) error {
|
||||
chain, err := data.GetChainByNetwork(mdb.NetworkAptos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
minConfirmations := int64(1)
|
||||
if chain != nil && chain.MinConfirmations > 0 {
|
||||
minConfirmations = int64(chain.MinConfirmations)
|
||||
}
|
||||
if latest-version+1 < minConfirmations {
|
||||
return fmt.Errorf("aptos transaction confirmations insufficient")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/GMWalletApp/epusdt/config"
|
||||
"github.com/GMWalletApp/epusdt/model/data"
|
||||
"github.com/GMWalletApp/epusdt/model/mdb"
|
||||
"github.com/GMWalletApp/epusdt/model/response"
|
||||
"github.com/GMWalletApp/epusdt/util/constant"
|
||||
"github.com/GMWalletApp/epusdt/util/sign"
|
||||
)
|
||||
|
||||
type EPayReturnRedirect struct {
|
||||
TargetURL string
|
||||
IsMerchantRedirect bool
|
||||
}
|
||||
|
||||
func isEPayOrder(order *mdb.Orders) bool {
|
||||
return order != nil && strings.EqualFold(order.PaymentType, mdb.PaymentTypeEpay)
|
||||
}
|
||||
|
||||
func buildCheckoutCounterPath(tradeID string) string {
|
||||
return "/pay/checkout-counter/" + tradeID
|
||||
}
|
||||
|
||||
func buildAbsoluteAppURL(path string) string {
|
||||
path = strings.TrimSpace(path)
|
||||
if path == "" {
|
||||
return ""
|
||||
}
|
||||
if !strings.HasPrefix(path, "/") {
|
||||
path = "/" + path
|
||||
}
|
||||
base := strings.TrimRight(strings.TrimSpace(config.GetAppUri()), "/")
|
||||
if base == "" {
|
||||
return path
|
||||
}
|
||||
return base + path
|
||||
}
|
||||
|
||||
func buildEPayReturnPath(tradeID string) string {
|
||||
return "/pay/return/" + tradeID
|
||||
}
|
||||
|
||||
func buildPublicRedirectURL(order *mdb.Orders) string {
|
||||
if order == nil {
|
||||
return ""
|
||||
}
|
||||
raw := strings.TrimSpace(order.RedirectUrl)
|
||||
if raw == "" || !isEPayOrder(order) {
|
||||
return raw
|
||||
}
|
||||
return buildAbsoluteAppURL(buildEPayReturnPath(order.TradeId))
|
||||
}
|
||||
|
||||
func ResolveOrderApiKey(order *mdb.Orders) (*mdb.ApiKey, error) {
|
||||
if order == nil || order.ApiKeyID == 0 {
|
||||
return nil, constant.OrderApiKeyUnavailableErr
|
||||
}
|
||||
row, err := data.GetApiKeyByID(order.ApiKeyID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if row == nil || row.ID == 0 || row.Status != mdb.ApiKeyStatusEnable {
|
||||
return nil, constant.OrderApiKeyUnavailableErr
|
||||
}
|
||||
return row, nil
|
||||
}
|
||||
|
||||
func BuildEPayResultParams(order *mdb.Orders, apiKeyRow *mdb.ApiKey) (map[string]string, error) {
|
||||
if order == nil || apiKeyRow == nil {
|
||||
return nil, constant.EPayReturnSignatureErr
|
||||
}
|
||||
|
||||
pidInt, err := strconv.Atoi(strings.TrimSpace(apiKeyRow.Pid))
|
||||
if err != nil {
|
||||
return nil, constant.EPayReturnSignatureErr
|
||||
}
|
||||
|
||||
notifyData := response.OrderNotifyResponseEpay{
|
||||
PID: pidInt,
|
||||
TradeNo: order.TradeId,
|
||||
OutTradeNo: order.OrderId,
|
||||
Type: "alipay",
|
||||
Name: order.Name,
|
||||
Money: fmt.Sprintf("%.4f", order.Amount),
|
||||
TradeStatus: "TRADE_SUCCESS",
|
||||
}
|
||||
|
||||
signstr, err := sign.Get(notifyData, apiKeyRow.SecretKey)
|
||||
if err != nil {
|
||||
return nil, constant.EPayReturnSignatureErr
|
||||
}
|
||||
|
||||
return map[string]string{
|
||||
"pid": strconv.Itoa(pidInt),
|
||||
"trade_no": notifyData.TradeNo,
|
||||
"out_trade_no": notifyData.OutTradeNo,
|
||||
"type": notifyData.Type,
|
||||
"name": notifyData.Name,
|
||||
"money": notifyData.Money,
|
||||
"trade_status": notifyData.TradeStatus,
|
||||
"sign": signstr,
|
||||
"sign_type": "MD5",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func appendQueryParams(rawURL string, params map[string]string) (string, error) {
|
||||
rawURL = strings.TrimSpace(rawURL)
|
||||
if rawURL == "" {
|
||||
return "", constant.OrderRedirectURLErr
|
||||
}
|
||||
|
||||
targetURL, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
return "", constant.OrderRedirectURLErr
|
||||
}
|
||||
|
||||
query := targetURL.Query()
|
||||
for key, value := range params {
|
||||
query.Set(key, value)
|
||||
}
|
||||
targetURL.RawQuery = query.Encode()
|
||||
return targetURL.String(), nil
|
||||
}
|
||||
|
||||
func ResolveEPayReturnRedirect(tradeID string) (*EPayReturnRedirect, error) {
|
||||
order, err := GetOrderInfoByTradeId(tradeID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !isEPayOrder(order) || order.Status != mdb.StatusPaySuccess {
|
||||
return &EPayReturnRedirect{
|
||||
TargetURL: buildCheckoutCounterPath(order.TradeId),
|
||||
}, nil
|
||||
}
|
||||
|
||||
rawMerchantURL := strings.TrimSpace(order.RedirectUrl)
|
||||
if rawMerchantURL == "" {
|
||||
return nil, constant.OrderRedirectURLErr
|
||||
}
|
||||
|
||||
apiKeyRow, err := ResolveOrderApiKey(order)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
params, err := BuildEPayResultParams(order, apiKeyRow)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
targetURL, err := appendQueryParams(rawMerchantURL, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &EPayReturnRedirect{
|
||||
TargetURL: targetURL,
|
||||
IsMerchantRedirect: true,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/GMWalletApp/epusdt/config"
|
||||
"github.com/GMWalletApp/epusdt/internal/testutil"
|
||||
"github.com/GMWalletApp/epusdt/model/dao"
|
||||
"github.com/GMWalletApp/epusdt/model/mdb"
|
||||
"github.com/GMWalletApp/epusdt/util/constant"
|
||||
"github.com/GMWalletApp/epusdt/util/sign"
|
||||
)
|
||||
|
||||
func TestBuildPublicRedirectURLRewritesOnlyEpayOrders(t *testing.T) {
|
||||
cleanup := testutil.SetupTestDatabases(t)
|
||||
defer cleanup()
|
||||
|
||||
epayOrder := &mdb.Orders{
|
||||
TradeId: "trade_epay_redirect",
|
||||
PaymentType: mdb.PaymentTypeEpay,
|
||||
RedirectUrl: "https://merchant.example/return",
|
||||
}
|
||||
wantRedirect := config.GetAppUri() + "/pay/return/trade_epay_redirect"
|
||||
if got := buildPublicRedirectURL(epayOrder); got != wantRedirect {
|
||||
t.Fatalf("epay redirect_url = %q, want internal return route", got)
|
||||
}
|
||||
|
||||
gmpayOrder := &mdb.Orders{
|
||||
TradeId: "trade_gmpay_redirect",
|
||||
PaymentType: mdb.PaymentTypeGmpay,
|
||||
RedirectUrl: "https://merchant.example/return",
|
||||
}
|
||||
if got := buildPublicRedirectURL(gmpayOrder); got != "https://merchant.example/return" {
|
||||
t.Fatalf("gmpay redirect_url = %q, want merchant raw redirect_url", got)
|
||||
}
|
||||
|
||||
if got := buildPublicRedirectURL(&mdb.Orders{TradeId: "trade_empty", PaymentType: mdb.PaymentTypeEpay}); got != "" {
|
||||
t.Fatalf("empty redirect_url = %q, want empty", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildEPayResultParamsMatchesLegacyFields(t *testing.T) {
|
||||
params, err := BuildEPayResultParams(&mdb.Orders{
|
||||
TradeId: "trade_epay_params",
|
||||
OrderId: "order_epay_params",
|
||||
Name: "VIP",
|
||||
Amount: 1,
|
||||
}, &mdb.ApiKey{
|
||||
Pid: "1001",
|
||||
SecretKey: "epay-secret",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("BuildEPayResultParams(): %v", err)
|
||||
}
|
||||
|
||||
expected := map[string]string{
|
||||
"pid": "1001",
|
||||
"trade_no": "trade_epay_params",
|
||||
"out_trade_no": "order_epay_params",
|
||||
"type": "alipay",
|
||||
"name": "VIP",
|
||||
"money": "1.0000",
|
||||
"trade_status": "TRADE_SUCCESS",
|
||||
"sign_type": "MD5",
|
||||
}
|
||||
for key, want := range expected {
|
||||
if got := params[key]; got != want {
|
||||
t.Fatalf("%s = %q, want %q", key, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
signParams := map[string]interface{}{
|
||||
"pid": params["pid"],
|
||||
"trade_no": params["trade_no"],
|
||||
"out_trade_no": params["out_trade_no"],
|
||||
"type": params["type"],
|
||||
"name": params["name"],
|
||||
"money": params["money"],
|
||||
"trade_status": params["trade_status"],
|
||||
}
|
||||
wantSign, err := sign.Get(signParams, "epay-secret")
|
||||
if err != nil {
|
||||
t.Fatalf("sign.Get(): %v", err)
|
||||
}
|
||||
if got := params["sign"]; got != wantSign {
|
||||
t.Fatalf("sign = %q, want %q", got, wantSign)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildEPayResultParamsRejectsNonNumericPid(t *testing.T) {
|
||||
_, err := BuildEPayResultParams(&mdb.Orders{
|
||||
TradeId: "trade_bad_pid",
|
||||
OrderId: "order_bad_pid",
|
||||
Name: "VIP",
|
||||
Amount: 1,
|
||||
}, &mdb.ApiKey{
|
||||
Pid: "not-a-number",
|
||||
SecretKey: "epay-secret",
|
||||
})
|
||||
if err != constant.EPayReturnSignatureErr {
|
||||
t.Fatalf("error = %v, want %v", err, constant.EPayReturnSignatureErr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveOrderApiKeyRejectsUnavailableRows(t *testing.T) {
|
||||
cleanup := testutil.SetupTestDatabases(t)
|
||||
defer cleanup()
|
||||
|
||||
if _, err := ResolveOrderApiKey(&mdb.Orders{TradeId: "missing_api_key_id"}); err != constant.OrderApiKeyUnavailableErr {
|
||||
t.Fatalf("missing api_key_id error = %v, want %v", err, constant.OrderApiKeyUnavailableErr)
|
||||
}
|
||||
|
||||
row := &mdb.ApiKey{
|
||||
Name: "disabled",
|
||||
Pid: "1201",
|
||||
SecretKey: "disabled-secret",
|
||||
Status: mdb.ApiKeyStatusDisable,
|
||||
}
|
||||
if err := dao.Mdb.Create(row).Error; err != nil {
|
||||
t.Fatalf("create disabled api key: %v", err)
|
||||
}
|
||||
if _, err := ResolveOrderApiKey(&mdb.Orders{TradeId: "disabled_api_key", ApiKeyID: row.ID}); err != constant.OrderApiKeyUnavailableErr {
|
||||
t.Fatalf("disabled api key error = %v, want %v", err, constant.OrderApiKeyUnavailableErr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendQueryParamsPreservesMerchantQuery(t *testing.T) {
|
||||
target, err := appendQueryParams("https://merchant.example/return?from=merchant", map[string]string{
|
||||
"pid": "1001",
|
||||
"trade_no": "trade_epay_redirect",
|
||||
"trade_status": "TRADE_SUCCESS",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("appendQueryParams(): %v", err)
|
||||
}
|
||||
|
||||
parsed, err := url.Parse(target)
|
||||
if err != nil {
|
||||
t.Fatalf("url.Parse(): %v", err)
|
||||
}
|
||||
query := parsed.Query()
|
||||
if got := query.Get("from"); got != "merchant" {
|
||||
t.Fatalf("from = %q, want merchant", got)
|
||||
}
|
||||
if got := query.Get("pid"); got != "1001" {
|
||||
t.Fatalf("pid = %q, want 1001", got)
|
||||
}
|
||||
if got := query.Get("trade_no"); got != "trade_epay_redirect" {
|
||||
t.Fatalf("trade_no = %q, want trade_epay_redirect", got)
|
||||
}
|
||||
if got := query.Get("trade_status"); got != "TRADE_SUCCESS" {
|
||||
t.Fatalf("trade_status = %q, want TRADE_SUCCESS", got)
|
||||
}
|
||||
}
|
||||
@@ -89,6 +89,8 @@ func validateManualOrderPaymentDefault(order *mdb.Orders, blockTransactionID str
|
||||
canonicalTxID, err = validateManualSolanaPayment(order, txID)
|
||||
case mdb.NetworkTon:
|
||||
canonicalTxID, err = validateManualTonPayment(order, txID)
|
||||
case mdb.NetworkAptos:
|
||||
canonicalTxID, err = ValidateManualAptosPayment(order, txID)
|
||||
case mdb.NetworkEthereum, mdb.NetworkBsc, mdb.NetworkPolygon, mdb.NetworkPlasma:
|
||||
canonicalTxID, err = validateManualEvmPayment(order, txID)
|
||||
default:
|
||||
@@ -123,7 +125,7 @@ func ensureManualBlockTransactionUnused(order *mdb.Orders, canonicalTxID string)
|
||||
|
||||
func manualBlockTransactionIDIsHex(network string) bool {
|
||||
switch strings.ToLower(strings.TrimSpace(network)) {
|
||||
case mdb.NetworkTron, mdb.NetworkEthereum, mdb.NetworkBsc, mdb.NetworkPolygon, mdb.NetworkPlasma:
|
||||
case mdb.NetworkTron, mdb.NetworkEthereum, mdb.NetworkBsc, mdb.NetworkPolygon, mdb.NetworkPlasma, mdb.NetworkAptos:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
@@ -149,7 +151,7 @@ func equivalentManualBlockTransactionIDs(network, canonicalTxID string) []string
|
||||
|
||||
add(canonicalTxID)
|
||||
switch network {
|
||||
case mdb.NetworkEthereum, mdb.NetworkBsc, mdb.NetworkPolygon, mdb.NetworkPlasma:
|
||||
case mdb.NetworkEthereum, mdb.NetworkBsc, mdb.NetworkPolygon, mdb.NetworkPlasma, mdb.NetworkAptos:
|
||||
body := strings.TrimPrefix(strings.TrimPrefix(canonicalTxID, "0x"), "0X")
|
||||
body = strings.ToLower(body)
|
||||
add("0x" + body)
|
||||
|
||||
@@ -113,6 +113,66 @@ func TestManualVerifyNormalizeTronTxIDAcceptsOptional0x(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateManualAptosPayment(t *testing.T) {
|
||||
cleanup := testutil.SetupTestDatabases(t)
|
||||
defer cleanup()
|
||||
|
||||
if err := dao.Mdb.Create(&mdb.Chain{Network: mdb.NetworkAptos, Enabled: true, MinConfirmations: 1}).Error; err != nil {
|
||||
t.Fatalf("create aptos chain: %v", err)
|
||||
}
|
||||
usdc := "0xbae207659db88bea0cbead6da0ed00aac12edcdda169e591cd41c94180b46f3b"
|
||||
if err := dao.Mdb.Create(&mdb.ChainToken{
|
||||
Network: mdb.NetworkAptos,
|
||||
Symbol: "USDC",
|
||||
ContractAddress: usdc,
|
||||
Decimals: 6,
|
||||
Enabled: true,
|
||||
}).Error; err != nil {
|
||||
t.Fatalf("create aptos usdc token: %v", err)
|
||||
}
|
||||
|
||||
receive, err := addressutil.NormalizeMoveAddress("0xa")
|
||||
if err != nil {
|
||||
t.Fatalf("normalize aptos receive: %v", err)
|
||||
}
|
||||
store, err := addressutil.NormalizeMoveAddress("0x11")
|
||||
if err != nil {
|
||||
t.Fatalf("normalize aptos store: %v", err)
|
||||
}
|
||||
senderStore, err := addressutil.NormalizeMoveAddress("0x12")
|
||||
if err != nil {
|
||||
t.Fatalf("normalize aptos sender store: %v", err)
|
||||
}
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
switch {
|
||||
case strings.HasPrefix(r.URL.Path, "/v1/transactions/by_hash/"):
|
||||
_, _ = w.Write([]byte(`{"type":"user_transaction","success":true,"hash":"0xabc","version":"101","timestamp":"1700000000123456","payload":{"function":"0x1::primary_fungible_store::transfer","arguments":[{"inner":"` + usdc + `"},"` + receive + `","4200000"],"type":"entry_function_payload"},"events":[{"type":"0x1::fungible_asset::Withdraw","data":{"amount":"4200000","store":"` + senderStore + `"}},{"type":"0x1::fungible_asset::Deposit","data":{"amount":"4200000","store":"` + store + `"}}],"changes":[{"address":"` + senderStore + `","data":{"type":"0x1::fungible_asset::FungibleStore","data":{"metadata":{"inner":"` + usdc + `"}}},"type":"write_resource"},{"address":"` + store + `","data":{"type":"0x1::fungible_asset::FungibleStore","data":{"metadata":{"inner":"` + usdc + `"}}},"type":"write_resource"},{"address":"` + store + `","data":{"type":"0x1::object::ObjectCore","data":{"owner":"` + receive + `"}},"type":"write_resource"}]}`))
|
||||
case r.URL.Path == "/v1":
|
||||
_, _ = w.Write([]byte(`{"ledger_version":"101"}`))
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
seedTestAptosRPCNode(t, server.URL, mdb.RpcNodePurposeGeneral, mdb.RpcNodeStatusOk, 100)
|
||||
|
||||
order := &mdb.Orders{
|
||||
BaseModel: mdb.BaseModel{ID: 1, CreatedAt: *carbon.NewTime(carbon.CreateFromTimestampMilli(1699999999000))},
|
||||
Network: mdb.NetworkAptos,
|
||||
Token: "USDC",
|
||||
ActualAmount: 4.2,
|
||||
ReceiveAddress: receive,
|
||||
}
|
||||
got, err := ValidateManualAptosPayment(order, "ABC")
|
||||
if err != nil {
|
||||
t.Fatalf("ValidateManualAptosPayment(): %v", err)
|
||||
}
|
||||
if got != "0xabc" {
|
||||
t.Fatalf("canonical tx id = %q, want %q", got, "0xabc")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseManualTonTxRefAcceptsCanonicalLTAndHashOnly(t *testing.T) {
|
||||
receiveRaw := "0:ba295e33b3c4c9b5265aa4ead1166a92931ce9abea120a8c5e91044a1257f89c"
|
||||
hash := strings.Repeat("a", 64)
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/GMWalletApp/epusdt/internal/testutil"
|
||||
"github.com/GMWalletApp/epusdt/model/dao"
|
||||
"github.com/GMWalletApp/epusdt/model/data"
|
||||
"github.com/GMWalletApp/epusdt/model/mdb"
|
||||
addressutil "github.com/GMWalletApp/epusdt/util/address"
|
||||
"github.com/dromara/carbon/v2"
|
||||
)
|
||||
|
||||
func seedTestAptosRPCNode(t *testing.T, url, purpose, status string, weight int) mdb.RpcNode {
|
||||
t.Helper()
|
||||
node := mdb.RpcNode{
|
||||
Network: mdb.NetworkAptos,
|
||||
Url: url,
|
||||
Type: mdb.RpcNodeTypeHttp,
|
||||
Weight: weight,
|
||||
Enabled: true,
|
||||
Purpose: purpose,
|
||||
Status: status,
|
||||
}
|
||||
if node.Purpose == "" {
|
||||
node.Purpose = mdb.RpcNodePurposeGeneral
|
||||
}
|
||||
if node.Status == "" {
|
||||
node.Status = mdb.RpcNodeStatusOk
|
||||
}
|
||||
if err := dao.Mdb.Create(&node).Error; err != nil {
|
||||
t.Fatalf("seed aptos rpc_nodes: %v", err)
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
func TestAptosGetLedgerVersionUsesRpcNodes(t *testing.T) {
|
||||
cleanup := testutil.SetupTestDatabases(t)
|
||||
defer cleanup()
|
||||
data.ResetRpcFailoverForTest()
|
||||
t.Cleanup(data.ResetRpcFailoverForTest)
|
||||
|
||||
var disabledCalls int
|
||||
disabled := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
disabledCalls++
|
||||
_, _ = w.Write([]byte(`{"ledger_version":"999"}`))
|
||||
}))
|
||||
defer disabled.Close()
|
||||
disabledNode := mdb.RpcNode{
|
||||
Network: mdb.NetworkAptos,
|
||||
Url: disabled.URL,
|
||||
Type: mdb.RpcNodeTypeHttp,
|
||||
Weight: 1000,
|
||||
Enabled: false,
|
||||
Purpose: mdb.RpcNodePurposeGeneral,
|
||||
Status: mdb.RpcNodeStatusOk,
|
||||
}
|
||||
if err := dao.Mdb.Create(&disabledNode).Error; err != nil {
|
||||
t.Fatalf("seed disabled aptos rpc_nodes: %v", err)
|
||||
}
|
||||
if err := dao.Mdb.Model(&mdb.RpcNode{}).Where("id = ?", disabledNode.ID).Update("enabled", false).Error; err != nil {
|
||||
t.Fatalf("disable aptos rpc_nodes: %v", err)
|
||||
}
|
||||
|
||||
var calls int
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
calls++
|
||||
if r.URL.Path != "/v1" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"ledger_version":"123"}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
seedTestAptosRPCNode(t, server.URL, mdb.RpcNodePurposeGeneral, mdb.RpcNodeStatusOk, 100)
|
||||
|
||||
got, err := AptosGetLedgerVersion()
|
||||
if err != nil {
|
||||
t.Fatalf("AptosGetLedgerVersion(): %v", err)
|
||||
}
|
||||
if got != 123 {
|
||||
t.Fatalf("ledger version = %d, want 123", got)
|
||||
}
|
||||
if calls != 1 {
|
||||
t.Fatalf("rpc calls = %d, want 1", calls)
|
||||
}
|
||||
if disabledCalls != 0 {
|
||||
t.Fatalf("disabled rpc calls = %d, want 0", disabledCalls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAptosGetTransactionsFallsBackToAlternateRpcNode(t *testing.T) {
|
||||
cleanup := testutil.SetupTestDatabases(t)
|
||||
defer cleanup()
|
||||
data.ResetRpcFailoverForTest()
|
||||
t.Cleanup(data.ResetRpcFailoverForTest)
|
||||
|
||||
var primaryCalls int
|
||||
primary := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
primaryCalls++
|
||||
http.Error(w, "temporary failure", http.StatusBadGateway)
|
||||
}))
|
||||
defer primary.Close()
|
||||
var backupCalls int
|
||||
backup := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
backupCalls++
|
||||
if r.URL.Path != "/v1/transactions" || r.URL.Query().Get("start") != "10" || r.URL.Query().Get("limit") != "2" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`[]`))
|
||||
}))
|
||||
defer backup.Close()
|
||||
seedTestAptosRPCNode(t, primary.URL, mdb.RpcNodePurposeGeneral, mdb.RpcNodeStatusOk, 100)
|
||||
seedTestAptosRPCNode(t, backup.URL, mdb.RpcNodePurposeGeneral, mdb.RpcNodeStatusOk, 1)
|
||||
|
||||
body, err := AptosGetTransactions(10, 2)
|
||||
if err != nil {
|
||||
t.Fatalf("AptosGetTransactions(): %v", err)
|
||||
}
|
||||
if string(body) != "[]" {
|
||||
t.Fatalf("transactions body = %s, want []", string(body))
|
||||
}
|
||||
if primaryCalls != 1 || backupCalls != 1 {
|
||||
t.Fatalf("rpc calls primary=%d backup=%d, want 1/1", primaryCalls, backupCalls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateManualAptosPaymentUsesManualRpcFallback(t *testing.T) {
|
||||
cleanup := testutil.SetupTestDatabases(t)
|
||||
defer cleanup()
|
||||
|
||||
if err := dao.Mdb.Create(&mdb.Chain{Network: mdb.NetworkAptos, Enabled: true, MinConfirmations: 1}).Error; err != nil {
|
||||
t.Fatalf("create aptos chain: %v", err)
|
||||
}
|
||||
const usdc = "0xbae207659db88bea0cbead6da0ed00aac12edcdda169e591cd41c94180b46f3b"
|
||||
upsertTestChainToken(t, mdb.ChainToken{
|
||||
Network: mdb.NetworkAptos,
|
||||
Symbol: "USDC",
|
||||
ContractAddress: usdc,
|
||||
Decimals: 6,
|
||||
Enabled: true,
|
||||
})
|
||||
|
||||
receive, err := addressutil.NormalizeMoveAddress("0xa")
|
||||
if err != nil {
|
||||
t.Fatalf("normalize aptos receive: %v", err)
|
||||
}
|
||||
store, err := addressutil.NormalizeMoveAddress("0x31")
|
||||
if err != nil {
|
||||
t.Fatalf("normalize aptos store: %v", err)
|
||||
}
|
||||
senderStore, err := addressutil.NormalizeMoveAddress("0x32")
|
||||
if err != nil {
|
||||
t.Fatalf("normalize aptos sender store: %v", err)
|
||||
}
|
||||
|
||||
var generalCalls int
|
||||
general := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
generalCalls++
|
||||
http.Error(w, "general unavailable", http.StatusBadGateway)
|
||||
}))
|
||||
defer general.Close()
|
||||
var manualCalls int
|
||||
manual := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
manualCalls++
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
switch {
|
||||
case strings.HasPrefix(r.URL.Path, "/v1/transactions/by_hash/"):
|
||||
_, _ = w.Write([]byte(`{"type":"user_transaction","success":true,"hash":"0xabc","version":"101","timestamp":"1700000000123456","payload":{"function":"0x1::primary_fungible_store::transfer","arguments":[{"inner":"` + usdc + `"},"` + receive + `","4200000"],"type":"entry_function_payload"},"events":[{"type":"0x1::fungible_asset::Withdraw","data":{"amount":"4200000","store":"` + senderStore + `"}},{"type":"0x1::fungible_asset::Deposit","data":{"amount":"4200000","store":"` + store + `"}}],"changes":[{"address":"` + senderStore + `","data":{"type":"0x1::fungible_asset::FungibleStore","data":{"metadata":{"inner":"` + usdc + `"}}},"type":"write_resource"},{"address":"` + store + `","data":{"type":"0x1::fungible_asset::FungibleStore","data":{"metadata":{"inner":"` + usdc + `"}}},"type":"write_resource"},{"address":"` + store + `","data":{"type":"0x1::object::ObjectCore","data":{"owner":"` + receive + `"}},"type":"write_resource"}]}`))
|
||||
case r.URL.Path == "/v1":
|
||||
_, _ = w.Write([]byte(`{"ledger_version":"101"}`))
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
}))
|
||||
defer manual.Close()
|
||||
seedTestAptosRPCNode(t, general.URL, mdb.RpcNodePurposeGeneral, mdb.RpcNodeStatusOk, 100)
|
||||
seedTestAptosRPCNode(t, manual.URL, mdb.RpcNodePurposeManualVerify, mdb.RpcNodeStatusOk, 1)
|
||||
|
||||
order := &mdb.Orders{
|
||||
BaseModel: mdb.BaseModel{ID: 1, CreatedAt: *carbon.NewTime(carbon.CreateFromTimestampMilli(1699999999000))},
|
||||
Network: mdb.NetworkAptos,
|
||||
Token: "USDC",
|
||||
ActualAmount: 4.2,
|
||||
ReceiveAddress: receive,
|
||||
}
|
||||
got, err := ValidateManualAptosPayment(order, "ABC")
|
||||
if err != nil {
|
||||
t.Fatalf("ValidateManualAptosPayment(): %v", err)
|
||||
}
|
||||
if got != "0xabc" {
|
||||
t.Fatalf("canonical tx id = %q, want %q", got, "0xabc")
|
||||
}
|
||||
if generalCalls != 1 || manualCalls != 2 {
|
||||
t.Fatalf("rpc calls general=%d manual=%d, want 1/2", generalCalls, manualCalls)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/GMWalletApp/epusdt/model/data"
|
||||
"github.com/GMWalletApp/epusdt/model/mdb"
|
||||
"github.com/GMWalletApp/epusdt/model/request"
|
||||
addressutil "github.com/GMWalletApp/epusdt/util/address"
|
||||
"github.com/GMWalletApp/epusdt/util/constant"
|
||||
"github.com/GMWalletApp/epusdt/util/log"
|
||||
)
|
||||
|
||||
var gProcessedMoveTx sync.Map // transfer-level key -> unix timestamp
|
||||
|
||||
var processMoveOrder = OrderProcessing
|
||||
|
||||
type MoveObservedTransfer struct {
|
||||
Network string
|
||||
ReceiveAddress string
|
||||
Token string
|
||||
RawAmount *big.Int
|
||||
Decimals int
|
||||
MinAmount float64
|
||||
Amount float64
|
||||
TxID string
|
||||
BlockTimeMs int64
|
||||
Version int64
|
||||
TransferKey string
|
||||
}
|
||||
|
||||
func ProcessMoveObservedTransferResult(transfer MoveObservedTransfer) error {
|
||||
net := moveLogLabel(transfer.Network)
|
||||
cacheKey := MoveTransferCacheKey(transfer)
|
||||
now := time.Now().Unix()
|
||||
if _, loaded := gProcessedMoveTx.LoadOrStore(cacheKey, now); loaded {
|
||||
return nil
|
||||
}
|
||||
clearCache := func() {
|
||||
gProcessedMoveTx.Delete(cacheKey)
|
||||
}
|
||||
if transfer.RawAmount == nil || transfer.RawAmount.Sign() <= 0 || transfer.Amount <= 0 {
|
||||
return nil
|
||||
}
|
||||
if transfer.MinAmount > 0 && transfer.Amount < transfer.MinAmount {
|
||||
log.Sugar.Debugf("[%s-%s][%s] skip below min amount tx=%s amount=%.2f min=%.2f", net, transfer.Token, transfer.ReceiveAddress, transfer.TxID, transfer.Amount, transfer.MinAmount)
|
||||
return nil
|
||||
}
|
||||
|
||||
tradeID, err := data.GetTradeIdByWalletAddressAndAmountAndToken(transfer.Network, transfer.ReceiveAddress, transfer.Token, transfer.Amount)
|
||||
if err != nil {
|
||||
clearCache()
|
||||
log.Sugar.Errorf("[%s-%s][%s] lock lookup failed tx=%s: %v", net, transfer.Token, transfer.ReceiveAddress, transfer.TxID, err)
|
||||
return err
|
||||
}
|
||||
if tradeID == "" {
|
||||
raw := ""
|
||||
if transfer.RawAmount != nil {
|
||||
raw = transfer.RawAmount.String()
|
||||
}
|
||||
log.Sugar.Infof("[%s-%s][%s] skip unmatched tx=%s amount=%.8f raw=%s decimals=%d", net, transfer.Token, transfer.ReceiveAddress, transfer.TxID, transfer.Amount, raw, transfer.Decimals)
|
||||
return nil
|
||||
}
|
||||
order, err := data.GetOrderInfoByTradeId(tradeID)
|
||||
if err != nil {
|
||||
clearCache()
|
||||
log.Sugar.Errorf("[%s-%s][%s] load order trade_id=%s tx=%s: %v", net, transfer.Token, transfer.ReceiveAddress, tradeID, transfer.TxID, err)
|
||||
return err
|
||||
}
|
||||
if err := EnsureMoveTransferMatchesOrder(order, transfer); err != nil {
|
||||
log.Sugar.Warnf("[%s-%s][%s] skip tx=%s trade_id=%s: %v", net, transfer.Token, transfer.ReceiveAddress, transfer.TxID, tradeID, err)
|
||||
return nil
|
||||
}
|
||||
req := &request.OrderProcessingRequest{
|
||||
ReceiveAddress: transfer.ReceiveAddress,
|
||||
Token: transfer.Token,
|
||||
Network: transfer.Network,
|
||||
TradeId: tradeID,
|
||||
Amount: transfer.Amount,
|
||||
BlockTransactionId: transfer.TxID,
|
||||
}
|
||||
if err = processMoveOrder(req); err != nil {
|
||||
if errors.Is(err, constant.OrderBlockAlreadyProcess) || errors.Is(err, constant.OrderStatusConflict) {
|
||||
log.Sugar.Infof("[%s-%s][%s] already resolved trade_id=%s tx=%s reason=%v", net, transfer.Token, transfer.ReceiveAddress, tradeID, transfer.TxID, err)
|
||||
return nil
|
||||
}
|
||||
clearCache()
|
||||
log.Sugar.Errorf("[%s-%s][%s] OrderProcessing failed trade_id=%s tx=%s: %v", net, transfer.Token, transfer.ReceiveAddress, tradeID, transfer.TxID, err)
|
||||
return err
|
||||
}
|
||||
sendPaymentNotification(order)
|
||||
log.Sugar.Infof("[%s-%s][%s] payment processed trade_id=%s tx=%s", net, transfer.Token, transfer.ReceiveAddress, tradeID, transfer.TxID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func MoveTransferCacheKey(transfer MoveObservedTransfer) string {
|
||||
if transfer.TransferKey != "" {
|
||||
return strings.ToLower(strings.TrimSpace(transfer.TransferKey))
|
||||
}
|
||||
receive := strings.ToLower(strings.TrimSpace(transfer.ReceiveAddress))
|
||||
token := strings.ToUpper(strings.TrimSpace(transfer.Token))
|
||||
raw := ""
|
||||
if transfer.RawAmount != nil {
|
||||
raw = transfer.RawAmount.String()
|
||||
}
|
||||
switch strings.ToLower(strings.TrimSpace(transfer.Network)) {
|
||||
case mdb.NetworkAptos:
|
||||
return fmt.Sprintf("aptos:%d:%s:%s:%s", transfer.Version, receive, token, raw)
|
||||
default:
|
||||
return strings.ToLower(strings.TrimSpace(transfer.Network)) + ":" + strings.TrimSpace(transfer.TxID)
|
||||
}
|
||||
}
|
||||
|
||||
func EnsureMoveTransferMatchesOrder(order *mdb.Orders, transfer MoveObservedTransfer) error {
|
||||
if order == nil || order.ID == 0 {
|
||||
return fmt.Errorf("order not found")
|
||||
}
|
||||
if strings.ToLower(strings.TrimSpace(order.Network)) != strings.ToLower(strings.TrimSpace(transfer.Network)) {
|
||||
return fmt.Errorf("network mismatch")
|
||||
}
|
||||
orderAddr, err := addressutil.NormalizeMoveAddress(order.ReceiveAddress)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid order receive address: %w", err)
|
||||
}
|
||||
transferAddr, err := addressutil.NormalizeMoveAddress(transfer.ReceiveAddress)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid transfer receive address: %w", err)
|
||||
}
|
||||
if orderAddr != transferAddr {
|
||||
return fmt.Errorf("transaction recipient mismatch")
|
||||
}
|
||||
if !strings.EqualFold(order.Token, transfer.Token) {
|
||||
return fmt.Errorf("token mismatch")
|
||||
}
|
||||
if transfer.BlockTimeMs <= 0 {
|
||||
return fmt.Errorf("transaction block time missing")
|
||||
}
|
||||
if transfer.BlockTimeMs < order.CreatedAt.TimestampMilli() {
|
||||
return fmt.Errorf("transaction predates the order")
|
||||
}
|
||||
if !amountMatchesRaw(order.ActualAmount, transfer.RawAmount, transfer.Decimals) {
|
||||
return fmt.Errorf("transaction amount mismatch")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func moveWalletWatched(address string, wallets map[string]struct{}) bool {
|
||||
if address == "" || len(wallets) == 0 {
|
||||
return false
|
||||
}
|
||||
_, ok := wallets[address]
|
||||
return ok
|
||||
}
|
||||
|
||||
func moveAptosTransferKey(version int64, eventIndex int, receive, token string, raw *big.Int) string {
|
||||
rawText := ""
|
||||
if raw != nil {
|
||||
rawText = raw.String()
|
||||
}
|
||||
return fmt.Sprintf("aptos:%d:%d:%s:%s:%s", version, eventIndex, strings.ToLower(strings.TrimSpace(receive)), strings.ToUpper(strings.TrimSpace(token)), rawText)
|
||||
}
|
||||
|
||||
func resolveMoveToken(network, assetID string, tokens []mdb.ChainToken) *mdb.ChainToken {
|
||||
assetID = addressutil.NormalizeMoveAssetID(assetID)
|
||||
for i := range tokens {
|
||||
contract := strings.TrimSpace(tokens[i].ContractAddress)
|
||||
if contract == "" {
|
||||
continue
|
||||
}
|
||||
if addressutil.MoveAssetMatches(assetID, contract) {
|
||||
return &tokens[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func firstMoveAddress(values ...string) string {
|
||||
for _, v := range values {
|
||||
if normalized, err := addressutil.NormalizeMoveAddress(v); err == nil {
|
||||
return normalized
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func firstNonEmpty(values ...string) string {
|
||||
for _, v := range values {
|
||||
if strings.TrimSpace(v) != "" {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func parseSignedBigInt(raw string) (*big.Int, bool) {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return nil, false
|
||||
}
|
||||
out, ok := new(big.Int).SetString(raw, 10)
|
||||
return out, ok
|
||||
}
|
||||
|
||||
func rawToFloat(raw *big.Int, decimals int) float64 {
|
||||
if raw == nil {
|
||||
return 0
|
||||
}
|
||||
value, _ := new(big.Rat).SetFrac(new(big.Int).Set(raw), new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(decimals)), nil)).Float64()
|
||||
return value
|
||||
}
|
||||
|
||||
func recordMoveRPCResult(network string, errp *error) {
|
||||
if errp != nil && *errp != nil {
|
||||
data.RecordRpcFailure(network)
|
||||
return
|
||||
}
|
||||
data.RecordRpcSuccess(network)
|
||||
}
|
||||
|
||||
func moveLogLabel(network string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(network)) {
|
||||
case mdb.NetworkAptos:
|
||||
return "APTOS"
|
||||
default:
|
||||
return strings.ToUpper(network)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/GMWalletApp/epusdt/model/mdb"
|
||||
addressutil "github.com/GMWalletApp/epusdt/util/address"
|
||||
"github.com/dromara/carbon/v2"
|
||||
)
|
||||
|
||||
func TestParseAptosTransfersUSDTAndUSDCFungibleEvents(t *testing.T) {
|
||||
receive, _ := addressutil.NormalizeMoveAddress("0xa")
|
||||
receive2, _ := addressutil.NormalizeMoveAddress("0xb")
|
||||
usdc := "0xbae207659db88bea0cbead6da0ed00aac12edcdda169e591cd41c94180b46f3b"
|
||||
usdt := "0x357b0b74bc833e95a115ad22604854d6b0fca151cecd94111770e5d6ffc9dc2b"
|
||||
usdcStore, _ := addressutil.NormalizeMoveAddress("0x12")
|
||||
usdcSenderStore, _ := addressutil.NormalizeMoveAddress("0x13")
|
||||
usdtStore, _ := addressutil.NormalizeMoveAddress("0x14")
|
||||
usdtSenderStore, _ := addressutil.NormalizeMoveAddress("0x15")
|
||||
noOwnerStore, _ := addressutil.NormalizeMoveAddress("0x16")
|
||||
body := []byte(`[
|
||||
{
|
||||
"type":"user_transaction",
|
||||
"success":true,
|
||||
"hash":"abc",
|
||||
"version":"101",
|
||||
"timestamp":"1700000000123456",
|
||||
"events":[
|
||||
{"type":"0x1::fungible_asset::Withdraw","data":{"amount":"2500000","store":"` + usdcSenderStore + `"}},
|
||||
{"type":"0x1::fungible_asset::Deposit","data":{"amount":"2500000","store":"` + usdcStore + `"}}
|
||||
],
|
||||
"changes":[
|
||||
{"address":"` + usdcSenderStore + `","data":{"type":"0x1::fungible_asset::FungibleStore","data":{"metadata":{"inner":"` + usdc + `"}}},"type":"write_resource"},
|
||||
{"address":"` + usdcStore + `","data":{"type":"0x1::fungible_asset::FungibleStore","data":{"metadata":{"inner":"` + usdc + `"}}},"type":"write_resource"},
|
||||
{"address":"` + usdcStore + `","data":{"type":"0x1::object::ObjectCore","data":{"owner":"0xa"}},"type":"write_resource"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type":"user_transaction",
|
||||
"success":true,
|
||||
"hash":"def",
|
||||
"version":"102",
|
||||
"timestamp":"1700000001123456",
|
||||
"payload":{"function":"0x1::primary_fungible_store::transfer","arguments":[{"inner":"` + usdt + `"},"` + receive2 + `","3100000"],"type":"entry_function_payload"},
|
||||
"events":[
|
||||
{"type":"0x1::fungible_asset::Withdraw","data":{"amount":"3100000","store":"` + usdtSenderStore + `"}},
|
||||
{"type":"0x1::fungible_asset::Deposit","data":{"amount":"3100000","store":"` + usdtStore + `"}}
|
||||
],
|
||||
"changes":[
|
||||
{"address":"` + usdtSenderStore + `","data":{"type":"0x1::fungible_asset::FungibleStore","data":{"metadata":{"inner":"` + usdt + `"}}},"type":"write_resource"},
|
||||
{"address":"` + usdtStore + `","data":{"type":"0x1::fungible_asset::FungibleStore","data":{"metadata":{"inner":"` + usdt + `"}}},"type":"write_resource"},
|
||||
{"address":"` + usdtStore + `","data":{"type":"0x1::object::ObjectCore","data":{"owner":"` + receive2 + `"}},"type":"write_resource"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type":"user_transaction",
|
||||
"success":true,
|
||||
"hash":"skip-withdraw-only",
|
||||
"version":"103",
|
||||
"timestamp":"1700000002123456",
|
||||
"events":[
|
||||
{"type":"0x1::fungible_asset::Withdraw","data":{"amount":"4100000","metadata_address":"` + usdc + `"}}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type":"user_transaction",
|
||||
"success":true,
|
||||
"hash":"skip-no-owner",
|
||||
"version":"104",
|
||||
"timestamp":"1700000003123456",
|
||||
"events":[
|
||||
{"type":"0x1::fungible_asset::Withdraw","data":{"amount":"5100000","store":"` + usdcSenderStore + `"}},
|
||||
{"type":"0x1::fungible_asset::Deposit","data":{"amount":"5100000","store":"` + noOwnerStore + `"}}
|
||||
],
|
||||
"changes":[
|
||||
{"address":"` + noOwnerStore + `","data":{"type":"0x1::fungible_asset::FungibleStore","data":{"metadata":{"inner":"` + usdc + `"}}},"type":"write_resource"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type":"user_transaction",
|
||||
"success":false,
|
||||
"hash":"skip-failed",
|
||||
"events":[
|
||||
{"type":"0x1::fungible_asset::Deposit","data":{"to":"` + receive + `","amount":"1","metadata_address":"` + usdc + `"}}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type":"user_transaction",
|
||||
"success":true,
|
||||
"hash":"skip-non-store-owner-fallback",
|
||||
"version":"105",
|
||||
"timestamp":"1700000004123456",
|
||||
"events":[
|
||||
{"type":"0x1::fungible_asset::Withdraw","data":{"amount":"6100000","metadata_address":"` + usdc + `"}},
|
||||
{"type":"0x1::fungible_asset::Deposit","data":{"to":"` + receive + `","amount":"6100000","metadata_address":"` + usdc + `"}}
|
||||
]
|
||||
}
|
||||
]`)
|
||||
tokens := []mdb.ChainToken{
|
||||
{Network: mdb.NetworkAptos, Symbol: "USDC", ContractAddress: usdc, Decimals: 6, Enabled: true, MinAmount: 1},
|
||||
{Network: mdb.NetworkAptos, Symbol: "USDT", ContractAddress: usdt, Decimals: 6, Enabled: true, MinAmount: 3},
|
||||
}
|
||||
|
||||
got, err := ParseAptosTransfers(body, receive, tokens)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseAptosTransfers: %v", err)
|
||||
}
|
||||
if len(got) != 1 {
|
||||
t.Fatalf("transfers len = %d, want 1: %#v", len(got), got)
|
||||
}
|
||||
if got[0].Token != "USDC" || got[0].Amount != 2.5 || got[0].TxID != "0xabc" || got[0].ReceiveAddress != receive {
|
||||
t.Fatalf("usdc transfer = %#v", got[0])
|
||||
}
|
||||
if got[0].TransferKey == "" {
|
||||
t.Fatalf("transfer key should be non-empty: %#v", got[0])
|
||||
}
|
||||
if got[0].MinAmount != 1 {
|
||||
t.Fatalf("usdc min amount = %.2f, want 1", got[0].MinAmount)
|
||||
}
|
||||
|
||||
gotSecondWallet, err := ParseAptosTransfers(body, receive2, tokens)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseAptosTransfers second wallet: %v", err)
|
||||
}
|
||||
if len(gotSecondWallet) != 1 || gotSecondWallet[0].Token != "USDT" || gotSecondWallet[0].Amount != 3.1 || gotSecondWallet[0].TxID != "0xdef" {
|
||||
t.Fatalf("usdt transfer = %#v", gotSecondWallet)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseAptosTransfersConfiguredCustomFungibleEvent(t *testing.T) {
|
||||
receive, _ := addressutil.NormalizeMoveAddress("0xa")
|
||||
custom := "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
store, _ := addressutil.NormalizeMoveAddress("0x21")
|
||||
senderStore, _ := addressutil.NormalizeMoveAddress("0x22")
|
||||
body := []byte(`[
|
||||
{
|
||||
"type":"user_transaction",
|
||||
"success":true,
|
||||
"hash":"custom",
|
||||
"version":"201",
|
||||
"timestamp":"1700000000123456",
|
||||
"events":[
|
||||
{"type":"0x1::fungible_asset::Withdraw","data":{"amount":"7500000","store":"` + senderStore + `"}},
|
||||
{"type":"0x1::fungible_asset::Deposit","data":{"amount":"7500000","store":"` + store + `"}}
|
||||
],
|
||||
"changes":[
|
||||
{"address":"` + senderStore + `","data":{"type":"0x1::fungible_asset::FungibleStore","data":{"metadata":{"inner":"` + custom + `"}}},"type":"write_resource"},
|
||||
{"address":"` + store + `","data":{"type":"0x1::fungible_asset::FungibleStore","data":{"metadata":{"inner":"` + custom + `"}}},"type":"write_resource"},
|
||||
{"address":"` + store + `","data":{"type":"0x1::object::ObjectCore","data":{"owner":"` + receive + `"}},"type":"write_resource"}
|
||||
]
|
||||
}
|
||||
]`)
|
||||
tokens := []mdb.ChainToken{
|
||||
{Network: mdb.NetworkAptos, Symbol: "MOVEUSD", ContractAddress: custom, Decimals: 6, Enabled: true},
|
||||
}
|
||||
|
||||
got, err := ParseAptosTransfers(body, receive, tokens)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseAptosTransfers: %v", err)
|
||||
}
|
||||
if len(got) != 1 {
|
||||
t.Fatalf("transfers len = %d, want 1: %#v", len(got), got)
|
||||
}
|
||||
if got[0].Token != "MOVEUSD" || got[0].Amount != 7.5 || got[0].TxID != "0xcustom" {
|
||||
t.Fatalf("custom transfer = %#v", got[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseAptosTransfersSplitDepositsMatchSingleWithdraw(t *testing.T) {
|
||||
receive, _ := addressutil.NormalizeMoveAddress("0xa")
|
||||
receive2, _ := addressutil.NormalizeMoveAddress("0xb")
|
||||
usdt := "0x357b0b74bc833e95a115ad22604854d6b0fca151cecd94111770e5d6ffc9dc2b"
|
||||
withdrawStore, _ := addressutil.NormalizeMoveAddress("0x21")
|
||||
depositStore, _ := addressutil.NormalizeMoveAddress("0x22")
|
||||
depositStore2, _ := addressutil.NormalizeMoveAddress("0x23")
|
||||
body := []byte(`[
|
||||
{
|
||||
"type":"user_transaction",
|
||||
"success":true,
|
||||
"hash":"split",
|
||||
"version":"201",
|
||||
"timestamp":"1700000000123456",
|
||||
"events":[
|
||||
{"type":"0x1::fungible_asset::Withdraw","data":{"amount":"3000000","store":"` + withdrawStore + `"}},
|
||||
{"type":"0x1::fungible_asset::Deposit","data":{"amount":"1000000","store":"` + depositStore + `"}},
|
||||
{"type":"0x1::fungible_asset::Deposit","data":{"amount":"2000000","store":"` + depositStore2 + `"}}
|
||||
],
|
||||
"changes":[
|
||||
{"address":"` + withdrawStore + `","data":{"type":"0x1::fungible_asset::FungibleStore","data":{"metadata":{"inner":"` + usdt + `"}}},"type":"write_resource"},
|
||||
{"address":"` + depositStore + `","data":{"type":"0x1::fungible_asset::FungibleStore","data":{"metadata":{"inner":"` + usdt + `"}}},"type":"write_resource"},
|
||||
{"address":"` + depositStore + `","data":{"type":"0x1::object::ObjectCore","data":{"owner":"` + receive + `"}},"type":"write_resource"},
|
||||
{"address":"` + depositStore2 + `","data":{"type":"0x1::fungible_asset::FungibleStore","data":{"metadata":{"inner":"` + usdt + `"}}},"type":"write_resource"},
|
||||
{"address":"` + depositStore2 + `","data":{"type":"0x1::object::ObjectCore","data":{"owner":"` + receive2 + `"}},"type":"write_resource"}
|
||||
]
|
||||
}
|
||||
]`)
|
||||
tokens := []mdb.ChainToken{
|
||||
{Network: mdb.NetworkAptos, Symbol: "USDT", ContractAddress: usdt, Decimals: 6, Enabled: true},
|
||||
}
|
||||
wallets := map[string]struct{}{receive: {}, receive2: {}}
|
||||
|
||||
got, err := ParseAptosTransfersForWallets(body, wallets, tokens)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseAptosTransfersForWallets: %v", err)
|
||||
}
|
||||
if len(got) != 2 {
|
||||
t.Fatalf("transfers len = %d, want 2: %#v", len(got), got)
|
||||
}
|
||||
if got[0].Token != "USDT" || got[0].Amount != 1 || got[0].ReceiveAddress != receive || got[0].TxID != "0xsplit" {
|
||||
t.Fatalf("first split transfer = %#v", got[0])
|
||||
}
|
||||
if got[1].Token != "USDT" || got[1].Amount != 2 || got[1].ReceiveAddress != receive2 || got[1].TxID != "0xsplit" {
|
||||
t.Fatalf("second split transfer = %#v", got[1])
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseAptosTransfersUsesStoreOwnerWhenGuidAccountIsZero(t *testing.T) {
|
||||
receive, _ := addressutil.NormalizeMoveAddress("0xa")
|
||||
usdt := "0x357b0b74bc833e95a115ad22604854d6b0fca151cecd94111770e5d6ffc9dc2b"
|
||||
store, _ := addressutil.NormalizeMoveAddress("0x31")
|
||||
senderStore, _ := addressutil.NormalizeMoveAddress("0x32")
|
||||
body := []byte(`[
|
||||
{
|
||||
"type":"user_transaction",
|
||||
"success":true,
|
||||
"hash":"decimal",
|
||||
"version":"301",
|
||||
"timestamp":"1700000000123456",
|
||||
"events":[
|
||||
{"guid":{"creation_number":"0","account_address":"0x0"},"sequence_number":"0","type":"0x1::fungible_asset::Withdraw","data":{"amount":"10000","store":"` + senderStore + `"}},
|
||||
{"guid":{"creation_number":"0","account_address":"0x0"},"sequence_number":"0","type":"0x1::fungible_asset::Deposit","data":{"amount":"10000","store":"` + store + `"}}
|
||||
],
|
||||
"changes":[
|
||||
{"address":"` + senderStore + `","data":{"type":"0x1::fungible_asset::FungibleStore","data":{"metadata":{"inner":"` + usdt + `"}}},"type":"write_resource"},
|
||||
{"address":"` + store + `","data":{"type":"0x1::fungible_asset::FungibleStore","data":{"metadata":{"inner":"` + usdt + `"}}},"type":"write_resource"},
|
||||
{"address":"` + store + `","data":{"type":"0x1::object::ObjectCore","data":{"owner":"` + receive + `"}},"type":"write_resource"}
|
||||
]
|
||||
}
|
||||
]`)
|
||||
tokens := []mdb.ChainToken{
|
||||
{Network: mdb.NetworkAptos, Symbol: "USDT", ContractAddress: usdt, Decimals: 6, Enabled: true},
|
||||
}
|
||||
|
||||
got, err := ParseAptosTransfers(body, receive, tokens)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseAptosTransfers: %v", err)
|
||||
}
|
||||
if len(got) != 1 {
|
||||
t.Fatalf("transfers len = %d, want 1: %#v", len(got), got)
|
||||
}
|
||||
if got[0].ReceiveAddress != receive || got[0].Decimals != 6 || got[0].Amount != 0.01 || got[0].RawAmount.String() != "10000" {
|
||||
t.Fatalf("transfer amount = %#v", got[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureMoveTransferMatchesOrder(t *testing.T) {
|
||||
receive, _ := addressutil.NormalizeMoveAddress("0xc")
|
||||
order := &mdb.Orders{
|
||||
BaseModel: mdb.BaseModel{ID: 1, CreatedAt: *carbon.NewTime(carbon.CreateFromTimestampMilli(time.Now().Add(-time.Minute).UnixMilli()))},
|
||||
Network: mdb.NetworkAptos,
|
||||
Token: "USDC",
|
||||
ActualAmount: 3.5,
|
||||
ReceiveAddress: strings.ToUpper(receive),
|
||||
}
|
||||
transfer := MoveObservedTransfer{
|
||||
Network: mdb.NetworkAptos,
|
||||
ReceiveAddress: receive,
|
||||
Token: "USDC",
|
||||
RawAmount: big.NewInt(3_500_000),
|
||||
Decimals: 6,
|
||||
BlockTimeMs: time.Now().UnixMilli(),
|
||||
}
|
||||
if err := EnsureMoveTransferMatchesOrder(order, transfer); err != nil {
|
||||
t.Fatalf("EnsureMoveTransferMatchesOrder: %v", err)
|
||||
}
|
||||
|
||||
transfer.RawAmount = big.NewInt(3_400_000)
|
||||
if err := EnsureMoveTransferMatchesOrder(order, transfer); err == nil || !strings.Contains(err.Error(), "amount") {
|
||||
t.Fatalf("amount mismatch err = %v, want amount error", err)
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,11 @@ func normalizeOrderAddressByNetwork(network, address string) string {
|
||||
return normalized
|
||||
}
|
||||
return address
|
||||
case mdb.NetworkAptos:
|
||||
if normalized, err := addressutil.NormalizeMoveAddress(address); err == nil {
|
||||
return normalized
|
||||
}
|
||||
return address
|
||||
default:
|
||||
return address
|
||||
}
|
||||
@@ -67,6 +72,9 @@ func ensureEnabledOrderAsset(network, token string) (*mdb.ChainToken, error) {
|
||||
if tokenRow == nil || tokenRow.ID == 0 {
|
||||
return nil, constant.SupportedAssetNotFound
|
||||
}
|
||||
if !ChainTokenReadyForPayment(*tokenRow) {
|
||||
return nil, constant.SupportedAssetNotFound
|
||||
}
|
||||
return tokenRow, nil
|
||||
}
|
||||
|
||||
@@ -706,7 +714,7 @@ func completeWaitSelectOrder(parent *mdb.Orders, token string, network string) (
|
||||
|
||||
func buildCheckoutResponse(order *mdb.Orders) *response.CheckoutCounterResponse {
|
||||
paymentType := mdb.PaymentTypeGmpay
|
||||
if strings.EqualFold(order.PaymentType, mdb.PaymentTypeEpay) {
|
||||
if isEPayOrder(order) {
|
||||
paymentType = mdb.PaymentTypeEpay
|
||||
}
|
||||
return &response.CheckoutCounterResponse{
|
||||
@@ -720,7 +728,7 @@ func buildCheckoutResponse(order *mdb.Orders) *response.CheckoutCounterResponse
|
||||
Status: order.Status,
|
||||
PaymentType: strings.ToLower(paymentType),
|
||||
ExpirationTime: order.CreatedAt.AddMinutes(config.GetOrderExpirationTime()).TimestampMilli(),
|
||||
RedirectUrl: order.RedirectUrl,
|
||||
RedirectUrl: buildPublicRedirectURL(order),
|
||||
CreatedAt: order.CreatedAt.TimestampMilli(),
|
||||
IsSelected: order.IsSelected,
|
||||
}
|
||||
|
||||
@@ -469,6 +469,56 @@ func TestCreateTransactionRejectsDisabledTonTokenBeforeRate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateTransactionRejectsEnabledMisconfiguredAptosUSDT(t *testing.T) {
|
||||
cleanup := testutil.SetupTestDatabases(t)
|
||||
defer cleanup()
|
||||
|
||||
if err := dao.Mdb.Create(&mdb.Chain{Network: mdb.NetworkAptos, Enabled: true}).Error; err != nil {
|
||||
t.Fatalf("create aptos chain: %v", err)
|
||||
}
|
||||
if err := dao.Mdb.Create(&mdb.ChainToken{
|
||||
Network: mdb.NetworkAptos,
|
||||
Symbol: "USDT",
|
||||
ContractAddress: "",
|
||||
Decimals: 6,
|
||||
Enabled: true,
|
||||
}).Error; err != nil {
|
||||
t.Fatalf("create aptos usdt token: %v", err)
|
||||
}
|
||||
|
||||
req := newCreateTransactionRequest("order_aptos_misconfigured_usdt_1", 10)
|
||||
req.Network = mdb.NetworkAptos
|
||||
req.Token = "USDT"
|
||||
|
||||
_, err := CreateTransaction(req, nil)
|
||||
if err != constant.SupportedAssetNotFound {
|
||||
t.Fatalf("create misconfigured Aptos USDT error = %v, want %v", err, constant.SupportedAssetNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureEnabledOrderAssetAllowsConfiguredAptosCustomToken(t *testing.T) {
|
||||
cleanup := testutil.SetupTestDatabases(t)
|
||||
defer cleanup()
|
||||
|
||||
if err := dao.Mdb.Create(&mdb.ChainToken{
|
||||
Network: mdb.NetworkAptos,
|
||||
Symbol: "MOVEUSD",
|
||||
ContractAddress: "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
Decimals: 6,
|
||||
Enabled: true,
|
||||
}).Error; err != nil {
|
||||
t.Fatalf("create aptos custom token: %v", err)
|
||||
}
|
||||
|
||||
token, err := ensureEnabledOrderAsset(mdb.NetworkAptos, "MOVEUSD")
|
||||
if err != nil {
|
||||
t.Fatalf("ensureEnabledOrderAsset(): %v", err)
|
||||
}
|
||||
if token.Symbol != "MOVEUSD" {
|
||||
t.Fatalf("token symbol = %q, want MOVEUSD", token.Symbol)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateTransactionSupportedTonTokenWithoutRateReturnsRateError(t *testing.T) {
|
||||
cleanup := testutil.SetupTestDatabases(t)
|
||||
defer cleanup()
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/GMWalletApp/epusdt/config"
|
||||
"github.com/GMWalletApp/epusdt/model/data"
|
||||
"github.com/GMWalletApp/epusdt/model/mdb"
|
||||
"github.com/GMWalletApp/epusdt/model/response"
|
||||
@@ -23,26 +20,7 @@ func GetCheckoutCounterByTradeId(tradeId string) (*response.CheckoutCounterRespo
|
||||
if orderInfo.ID <= 0 {
|
||||
return nil, ErrOrder
|
||||
}
|
||||
paymentType := mdb.PaymentTypeGmpay
|
||||
if strings.EqualFold(orderInfo.PaymentType, mdb.PaymentTypeEpay) {
|
||||
paymentType = mdb.PaymentTypeEpay
|
||||
}
|
||||
|
||||
resp := &response.CheckoutCounterResponse{
|
||||
TradeId: orderInfo.TradeId,
|
||||
Amount: orderInfo.Amount,
|
||||
ActualAmount: orderInfo.ActualAmount,
|
||||
Token: orderInfo.Token,
|
||||
Currency: orderInfo.Currency,
|
||||
ReceiveAddress: orderInfo.ReceiveAddress,
|
||||
Network: orderInfo.Network,
|
||||
Status: orderInfo.Status,
|
||||
PaymentType: strings.ToLower(paymentType),
|
||||
ExpirationTime: orderInfo.CreatedAt.AddMinutes(config.GetOrderExpirationTime()).TimestampMilli(),
|
||||
RedirectUrl: orderInfo.RedirectUrl,
|
||||
CreatedAt: orderInfo.CreatedAt.TimestampMilli(),
|
||||
IsSelected: orderInfo.IsSelected,
|
||||
}
|
||||
resp := buildCheckoutResponse(orderInfo)
|
||||
if orderInfo.PayProvider == mdb.PaymentProviderOkPay {
|
||||
providerRow, rowErr := data.GetProviderOrderByTradeIDAndProvider(orderInfo.TradeId, mdb.PaymentProviderOkPay)
|
||||
if rowErr != nil {
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/GMWalletApp/epusdt/model/mdb"
|
||||
)
|
||||
|
||||
func ChainTokenReadyForPayment(token mdb.ChainToken) bool {
|
||||
if strings.TrimSpace(token.Symbol) == "" {
|
||||
return false
|
||||
}
|
||||
if chainTokenRequiresAssetID(token) && strings.TrimSpace(token.ContractAddress) == "" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func chainTokenRequiresAssetID(token mdb.ChainToken) bool {
|
||||
network := strings.ToLower(strings.TrimSpace(token.Network))
|
||||
symbol := strings.ToUpper(strings.TrimSpace(token.Symbol))
|
||||
switch network {
|
||||
case mdb.NetworkTron:
|
||||
return symbol != "TRX"
|
||||
case mdb.NetworkSolana:
|
||||
return symbol != "SOL"
|
||||
case mdb.NetworkTon:
|
||||
return symbol != TonNativeSymbol
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
+6
-40
@@ -2,9 +2,7 @@ package mq
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -13,6 +11,7 @@ import (
|
||||
"github.com/GMWalletApp/epusdt/model/data"
|
||||
"github.com/GMWalletApp/epusdt/model/mdb"
|
||||
"github.com/GMWalletApp/epusdt/model/response"
|
||||
"github.com/GMWalletApp/epusdt/model/service"
|
||||
"github.com/GMWalletApp/epusdt/util/http_client"
|
||||
"github.com/GMWalletApp/epusdt/util/log"
|
||||
"github.com/GMWalletApp/epusdt/util/sign"
|
||||
@@ -25,18 +24,12 @@ import (
|
||||
// other secret would produce a signature the merchant can't verify.
|
||||
// The admin can resend the callback after fixing the key.
|
||||
func resolveOrderApiKey(order *mdb.Orders) (*mdb.ApiKey, error) {
|
||||
if order.ApiKeyID == 0 {
|
||||
return nil, fmt.Errorf("order trade_id=%s has no api_key_id", order.TradeId)
|
||||
}
|
||||
row, err := data.GetApiKeyByID(order.ApiKeyID)
|
||||
row, err := service.ResolveOrderApiKey(order)
|
||||
if err != nil || row == nil || row.ID == 0 {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("lookup api_key_id=%d: %w", order.ApiKeyID, err)
|
||||
return nil, err
|
||||
}
|
||||
if row.ID == 0 {
|
||||
return nil, fmt.Errorf("api_key_id=%d not found (deleted?)", order.ApiKeyID)
|
||||
}
|
||||
if row.Status != mdb.ApiKeyStatusEnable {
|
||||
return nil, fmt.Errorf("api_key_id=%d is disabled", order.ApiKeyID)
|
||||
return nil, errors.New("api key unavailable")
|
||||
}
|
||||
return row, nil
|
||||
}
|
||||
@@ -207,38 +200,11 @@ func sendOrderCallback(order *mdb.Orders) error {
|
||||
|
||||
switch {
|
||||
case strings.EqualFold(order.PaymentType, mdb.PaymentTypeEpay):
|
||||
// EPAY uses pid (integer) and secret_key as "key".
|
||||
pidInt, convErr := strconv.Atoi(apiKeyRow.Pid)
|
||||
if convErr != nil {
|
||||
return fmt.Errorf("epay pid not numeric: %s", apiKeyRow.Pid)
|
||||
}
|
||||
notifyData := response.OrderNotifyResponseEpay{
|
||||
PID: pidInt,
|
||||
TradeNo: order.TradeId,
|
||||
OutTradeNo: order.OrderId,
|
||||
Type: "alipay",
|
||||
Name: order.Name,
|
||||
Money: fmt.Sprintf("%.4f", order.Amount),
|
||||
TradeStatus: "TRADE_SUCCESS",
|
||||
}
|
||||
|
||||
signstr2, err := sign.Get(notifyData, apiKeyRow.SecretKey)
|
||||
formData, err := service.BuildEPayResultParams(order, apiKeyRow)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
formData := map[string]string{
|
||||
"pid": fmt.Sprintf("%d", notifyData.PID),
|
||||
"trade_no": notifyData.TradeNo,
|
||||
"out_trade_no": notifyData.OutTradeNo,
|
||||
"type": notifyData.Type,
|
||||
"name": notifyData.Name,
|
||||
"money": notifyData.Money,
|
||||
"trade_status": notifyData.TradeStatus,
|
||||
"sign": signstr2,
|
||||
"sign_type": "MD5",
|
||||
}
|
||||
|
||||
epayResp, err := http_client.GetHttpClient().R().SetQueryParams(formData).Get(order.NotifyUrl)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -37,6 +37,7 @@ func RegisterRoute(e *echo.Echo) {
|
||||
})
|
||||
|
||||
payRoute.GET("/checkout-counter-resp/:trade_id", comm.Ctrl.CheckoutCounter)
|
||||
payRoute.GET("/return/:trade_id", comm.Ctrl.ReturnToMerchant)
|
||||
payRoute.GET("/check-status/:trade_id", comm.Ctrl.CheckStatus)
|
||||
payRoute.POST("/submit-tx-hash/:trade_id", comm.Ctrl.SubmitTxHash)
|
||||
payRoute.POST("/switch-network", comm.Ctrl.SwitchNetwork)
|
||||
|
||||
@@ -224,6 +224,37 @@ func doFormPost(e *echo.Echo, path string, values url.Values) *httptest.Response
|
||||
return rec
|
||||
}
|
||||
|
||||
func mustCreateEPayOrder(t *testing.T, e *echo.Echo, orderID string, returnURL string, extra url.Values) string {
|
||||
t.Helper()
|
||||
|
||||
values := url.Values{
|
||||
"pid": {"1"},
|
||||
"name": {orderID},
|
||||
"type": {"alipay"},
|
||||
"money": {"1.00"},
|
||||
"out_trade_no": {orderID},
|
||||
"notify_url": {"https://93.184.216.34/notify"},
|
||||
"return_url": {returnURL},
|
||||
}
|
||||
for key, items := range extra {
|
||||
clone := append([]string(nil), items...)
|
||||
values[key] = clone
|
||||
}
|
||||
values = signEpayValues(values)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/payments/epay/v1/order/create-transaction/submit.php?"+values.Encode(), nil)
|
||||
rec := httptest.NewRecorder()
|
||||
e.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusFound {
|
||||
t.Fatalf("create epay order failed: %d %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
location := rec.Header().Get("Location")
|
||||
if !strings.HasPrefix(location, "/pay/checkout-counter/") {
|
||||
t.Fatalf("expected checkout redirect, got %q", location)
|
||||
}
|
||||
return strings.TrimPrefix(location, "/pay/checkout-counter/")
|
||||
}
|
||||
|
||||
func TestRootPostRoute(t *testing.T) {
|
||||
e := setupTestEnv(t)
|
||||
|
||||
@@ -1245,6 +1276,12 @@ func TestEpaySubmitPhpGetCompatible(t *testing.T) {
|
||||
if checkoutData["payment_type"] != "epay" {
|
||||
t.Fatalf("epay checkout payment_type = %v, want epay", checkoutData["payment_type"])
|
||||
}
|
||||
if got, _ := checkoutData["redirect_url"].(string); got != "http://localhost:8080/pay/return/"+tradeID {
|
||||
t.Fatalf("epay checkout redirect_url = %q, want internal return route", got)
|
||||
}
|
||||
if order.RedirectUrl != "http://localhost/return" {
|
||||
t.Fatalf("stored redirect_url = %q, want merchant raw return_url", order.RedirectUrl)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEpaySubmitPhpRequestTokenNetworkOverrideDefaults(t *testing.T) {
|
||||
@@ -1526,6 +1563,234 @@ func TestSubmitTxHash_SuccessUpdatesCheckStatus(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPayReturn_NotFound(t *testing.T) {
|
||||
e := setupTestEnv(t)
|
||||
req := httptest.NewRequest(http.MethodGet, "/pay/return/nonexistent-trade-id", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
e.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400, got %d: %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
resp := parseResp(t, rec)
|
||||
if got := int(resp["status_code"].(float64)); got != 10008 {
|
||||
t.Fatalf("status_code = %d, want 10008; response=%v", got, resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPayReturn_PaidEpayRedirectsToMerchantWithSignedParams(t *testing.T) {
|
||||
e := setupTestEnv(t)
|
||||
tradeID := mustCreateEPayOrder(t, e, "epay-return-paid-001", "https://merchant.example/return?from=merchant", url.Values{
|
||||
"token": {"usdt"},
|
||||
"network": {"tron"},
|
||||
"currency": {"cny"},
|
||||
})
|
||||
if err := dao.Mdb.Model(&mdb.Orders{}).
|
||||
Where("trade_id = ?", tradeID).
|
||||
Update("status", mdb.StatusPaySuccess).Error; err != nil {
|
||||
t.Fatalf("mark order paid: %v", err)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/pay/return/"+tradeID, nil)
|
||||
rec := httptest.NewRecorder()
|
||||
e.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusFound {
|
||||
t.Fatalf("expected 302, got %d: %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
if got := rec.Header().Get("Cache-Control"); got != "no-store" {
|
||||
t.Fatalf("cache-control = %q, want no-store", got)
|
||||
}
|
||||
|
||||
location := rec.Header().Get("Location")
|
||||
targetURL, err := url.Parse(location)
|
||||
if err != nil {
|
||||
t.Fatalf("parse redirect location: %v", err)
|
||||
}
|
||||
if targetURL.Scheme != "https" || targetURL.Host != "merchant.example" || targetURL.Path != "/return" {
|
||||
t.Fatalf("unexpected redirect target: %q", location)
|
||||
}
|
||||
|
||||
query := targetURL.Query()
|
||||
if query.Get("from") != "merchant" {
|
||||
t.Fatalf("preserved query = %q, want merchant", query.Get("from"))
|
||||
}
|
||||
if query.Get("pid") != "1" {
|
||||
t.Fatalf("pid = %q, want 1", query.Get("pid"))
|
||||
}
|
||||
if query.Get("trade_no") != tradeID {
|
||||
t.Fatalf("trade_no = %q, want %q", query.Get("trade_no"), tradeID)
|
||||
}
|
||||
if query.Get("out_trade_no") != "epay-return-paid-001" {
|
||||
t.Fatalf("out_trade_no = %q", query.Get("out_trade_no"))
|
||||
}
|
||||
if query.Get("type") != "alipay" {
|
||||
t.Fatalf("type = %q, want alipay", query.Get("type"))
|
||||
}
|
||||
if query.Get("name") != "epay-return-paid-001" {
|
||||
t.Fatalf("name = %q", query.Get("name"))
|
||||
}
|
||||
if query.Get("money") != "1.0000" {
|
||||
t.Fatalf("money = %q, want 1.0000", query.Get("money"))
|
||||
}
|
||||
if query.Get("trade_status") != "TRADE_SUCCESS" {
|
||||
t.Fatalf("trade_status = %q, want TRADE_SUCCESS", query.Get("trade_status"))
|
||||
}
|
||||
if query.Get("sign_type") != "MD5" {
|
||||
t.Fatalf("sign_type = %q, want MD5", query.Get("sign_type"))
|
||||
}
|
||||
|
||||
signParams := map[string]interface{}{
|
||||
"pid": query.Get("pid"),
|
||||
"trade_no": query.Get("trade_no"),
|
||||
"out_trade_no": query.Get("out_trade_no"),
|
||||
"type": query.Get("type"),
|
||||
"name": query.Get("name"),
|
||||
"money": query.Get("money"),
|
||||
"trade_status": query.Get("trade_status"),
|
||||
}
|
||||
calcSig, err := sign.Get(signParams, testAPIToken)
|
||||
if err != nil {
|
||||
t.Fatalf("calc epay return signature: %v", err)
|
||||
}
|
||||
if got := query.Get("sign"); got != calcSig {
|
||||
t.Fatalf("sign = %q, want %q", got, calcSig)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPayReturn_UnpaidEpayRedirectsBackToCheckout(t *testing.T) {
|
||||
e := setupTestEnv(t)
|
||||
tradeID := mustCreateEPayOrder(t, e, "epay-return-unpaid-001", "https://merchant.example/return", url.Values{
|
||||
"token": {"usdt"},
|
||||
"network": {"tron"},
|
||||
"currency": {"cny"},
|
||||
})
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/pay/return/"+tradeID, nil)
|
||||
rec := httptest.NewRecorder()
|
||||
e.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusFound {
|
||||
t.Fatalf("expected 302, got %d: %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
if got := rec.Header().Get("Location"); got != "/pay/checkout-counter/"+tradeID {
|
||||
t.Fatalf("location = %q, want checkout counter", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPayReturn_PaidNonEpayRedirectsBackToCheckout(t *testing.T) {
|
||||
e := setupTestEnv(t)
|
||||
tradeID := createCheckoutCounterRespTestOrder(t, e, "pay-return-gmpay-001")
|
||||
if err := dao.Mdb.Model(&mdb.Orders{}).
|
||||
Where("trade_id = ?", tradeID).
|
||||
Update("status", mdb.StatusPaySuccess).Error; err != nil {
|
||||
t.Fatalf("mark order paid: %v", err)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/pay/return/"+tradeID, nil)
|
||||
rec := httptest.NewRecorder()
|
||||
e.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusFound {
|
||||
t.Fatalf("expected 302, got %d: %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
if got := rec.Header().Get("Location"); got != "/pay/checkout-counter/"+tradeID {
|
||||
t.Fatalf("location = %q, want checkout counter", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPayReturn_PaidEpayWithEmptyRedirectReturnsError(t *testing.T) {
|
||||
e := setupTestEnv(t)
|
||||
tradeID := mustCreateEPayOrder(t, e, "epay-return-empty-001", "https://merchant.example/return", url.Values{
|
||||
"token": {"usdt"},
|
||||
"network": {"tron"},
|
||||
"currency": {"cny"},
|
||||
})
|
||||
if err := dao.Mdb.Model(&mdb.Orders{}).
|
||||
Where("trade_id = ?", tradeID).
|
||||
Updates(map[string]interface{}{
|
||||
"status": mdb.StatusPaySuccess,
|
||||
"redirect_url": "",
|
||||
}).Error; err != nil {
|
||||
t.Fatalf("update order: %v", err)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/pay/return/"+tradeID, nil)
|
||||
rec := httptest.NewRecorder()
|
||||
e.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400, got %d: %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
resp := parseResp(t, rec)
|
||||
if got := int(resp["status_code"].(float64)); got != 10044 {
|
||||
t.Fatalf("status_code = %d, want 10044; response=%v", got, resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPayReturn_PaidEpayWithDisabledApiKeyReturnsError(t *testing.T) {
|
||||
e := setupTestEnv(t)
|
||||
tradeID := mustCreateEPayOrder(t, e, "epay-return-disabled-key-001", "https://merchant.example/return", url.Values{
|
||||
"token": {"usdt"},
|
||||
"network": {"tron"},
|
||||
"currency": {"cny"},
|
||||
})
|
||||
order, err := data.GetOrderInfoByTradeId(tradeID)
|
||||
if err != nil {
|
||||
t.Fatalf("load order: %v", err)
|
||||
}
|
||||
if err := dao.Mdb.Model(&mdb.Orders{}).
|
||||
Where("trade_id = ?", tradeID).
|
||||
Update("status", mdb.StatusPaySuccess).Error; err != nil {
|
||||
t.Fatalf("mark order paid: %v", err)
|
||||
}
|
||||
if err := dao.Mdb.Model(&mdb.ApiKey{}).
|
||||
Where("id = ?", order.ApiKeyID).
|
||||
Update("status", mdb.ApiKeyStatusDisable).Error; err != nil {
|
||||
t.Fatalf("disable api key: %v", err)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/pay/return/"+tradeID, nil)
|
||||
rec := httptest.NewRecorder()
|
||||
e.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400, got %d: %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
resp := parseResp(t, rec)
|
||||
if got := int(resp["status_code"].(float64)); got != 10045 {
|
||||
t.Fatalf("status_code = %d, want 10045; response=%v", got, resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPayReturn_PaidEpayWithNonNumericPidReturnsSignatureError(t *testing.T) {
|
||||
e := setupTestEnv(t)
|
||||
tradeID := mustCreateEPayOrder(t, e, "epay-return-bad-pid-001", "https://merchant.example/return", url.Values{
|
||||
"token": {"usdt"},
|
||||
"network": {"tron"},
|
||||
"currency": {"cny"},
|
||||
})
|
||||
order, err := data.GetOrderInfoByTradeId(tradeID)
|
||||
if err != nil {
|
||||
t.Fatalf("load order: %v", err)
|
||||
}
|
||||
if err := dao.Mdb.Model(&mdb.Orders{}).
|
||||
Where("trade_id = ?", tradeID).
|
||||
Update("status", mdb.StatusPaySuccess).Error; err != nil {
|
||||
t.Fatalf("mark order paid: %v", err)
|
||||
}
|
||||
if err := dao.Mdb.Model(&mdb.ApiKey{}).
|
||||
Where("id = ?", order.ApiKeyID).
|
||||
Update("pid", "not-a-number").Error; err != nil {
|
||||
t.Fatalf("break pid: %v", err)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/pay/return/"+tradeID, nil)
|
||||
rec := httptest.NewRecorder()
|
||||
e.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400, got %d: %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
resp := parseResp(t, rec)
|
||||
if got := int(resp["status_code"].(float64)); got != 10046 {
|
||||
t.Fatalf("status_code = %d, want 10046; response=%v", got, resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubmitTxHash_VerificationFailureAllowsRetrySameHash(t *testing.T) {
|
||||
e := setupTestEnv(t)
|
||||
tradeID := createCheckoutCounterRespTestOrder(t, e, "submit-tx-hash-retry-001")
|
||||
@@ -2225,6 +2490,9 @@ func TestSwitchNetwork_EpayPlaceholderCompletesChainInPlace(t *testing.T) {
|
||||
if got, _ := respData["trade_id"].(string); got != tradeID {
|
||||
t.Fatalf("switch trade_id = %q, want parent %q", got, tradeID)
|
||||
}
|
||||
if got, _ := respData["redirect_url"].(string); got != "http://localhost:8080/pay/return/"+tradeID {
|
||||
t.Fatalf("switch redirect_url = %q, want internal return route", got)
|
||||
}
|
||||
|
||||
parent, err := data.GetOrderInfoByTradeId(tradeID)
|
||||
if err != nil {
|
||||
@@ -2233,6 +2501,9 @@ func TestSwitchNetwork_EpayPlaceholderCompletesChainInPlace(t *testing.T) {
|
||||
if parent.Status != mdb.StatusWaitPay || parent.PaymentType != mdb.PaymentTypeEpay || parent.Network != mdb.NetworkTron || parent.Token != "USDT" || parent.ReceiveAddress == "" {
|
||||
t.Fatalf("parent fields = status %d payment_type %q network %q token %q address %q", parent.Status, parent.PaymentType, parent.Network, parent.Token, parent.ReceiveAddress)
|
||||
}
|
||||
if parent.RedirectUrl != "http://localhost/return" {
|
||||
t.Fatalf("stored redirect_url = %q, want merchant raw return_url", parent.RedirectUrl)
|
||||
}
|
||||
count, err := data.CountActiveSubOrders(tradeID)
|
||||
if err != nil {
|
||||
t.Fatalf("count active sub-orders: %v", err)
|
||||
@@ -2315,6 +2586,9 @@ func TestSwitchNetwork_OkPayFromEpayWaitSelectPlaceholder(t *testing.T) {
|
||||
if respData["payment_url"] != "https://pay.okaypay.test/epay-placeholder" {
|
||||
t.Fatalf("payment_url = %v, want okpay url", respData["payment_url"])
|
||||
}
|
||||
if got, _ := respData["redirect_url"].(string); got != "http://localhost:8080/pay/return/"+tradeID {
|
||||
t.Fatalf("switch redirect_url = %q, want internal return route", got)
|
||||
}
|
||||
|
||||
parent, err := data.GetOrderInfoByTradeId(tradeID)
|
||||
if err != nil {
|
||||
@@ -2323,6 +2597,9 @@ func TestSwitchNetwork_OkPayFromEpayWaitSelectPlaceholder(t *testing.T) {
|
||||
if parent.PaymentType != mdb.PaymentTypeEpay || parent.PayProvider != mdb.PaymentProviderOkPay || parent.Network != mdb.PaymentProviderOkPay || parent.ReceiveAddress != "OKPAY" {
|
||||
t.Fatalf("parent fields = payment_type %q provider %q network %q address %q", parent.PaymentType, parent.PayProvider, parent.Network, parent.ReceiveAddress)
|
||||
}
|
||||
if parent.RedirectUrl != "http://localhost/return" {
|
||||
t.Fatalf("stored redirect_url = %q, want merchant raw return_url", parent.RedirectUrl)
|
||||
}
|
||||
count, err := data.CountActiveSubOrders(tradeID)
|
||||
if err != nil {
|
||||
t.Fatalf("count active sub-orders: %v", err)
|
||||
|
||||
@@ -15,6 +15,7 @@ func Start() {
|
||||
go StartPlasmaWebSocketListener()
|
||||
go StartTronBlockScannerListener()
|
||||
go StartTonBlockScannerListener()
|
||||
go StartAptosLedgerScannerListener()
|
||||
|
||||
c := cron.New()
|
||||
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/GMWalletApp/epusdt/model/data"
|
||||
"github.com/GMWalletApp/epusdt/model/mdb"
|
||||
"github.com/GMWalletApp/epusdt/model/service"
|
||||
"github.com/GMWalletApp/epusdt/util/log"
|
||||
)
|
||||
|
||||
const (
|
||||
aptosLedgerChunkSize int64 = 100
|
||||
aptosLedgerWorkerCount = 3
|
||||
aptosLedgerQueueLimit = 100
|
||||
)
|
||||
|
||||
type aptosRuntimeCursor struct {
|
||||
initialized bool
|
||||
lastSeenVersion int64
|
||||
}
|
||||
|
||||
type aptosLedgerRange struct {
|
||||
start int64
|
||||
limit int64
|
||||
state moveWatchState
|
||||
}
|
||||
|
||||
type aptosChainProvider interface {
|
||||
LatestLedgerVersion() (int64, error)
|
||||
Transactions(start int64, limit int64) ([]byte, error)
|
||||
}
|
||||
|
||||
type serviceAptosProvider struct{}
|
||||
|
||||
func (serviceAptosProvider) LatestLedgerVersion() (int64, error) {
|
||||
return service.AptosGetLedgerVersion()
|
||||
}
|
||||
|
||||
func (serviceAptosProvider) Transactions(start int64, limit int64) ([]byte, error) {
|
||||
return service.AptosGetTransactions(start, limit)
|
||||
}
|
||||
|
||||
var (
|
||||
parseAptosTransfersForWallets = service.ParseAptosTransfersForWallets
|
||||
processAptosObservedTransferFunc = service.ProcessMoveObservedTransferResult
|
||||
)
|
||||
|
||||
func StartAptosLedgerScannerListener() {
|
||||
provider := serviceAptosProvider{}
|
||||
cursor := &aptosRuntimeCursor{}
|
||||
log.Sugar.Infof("[APTOS] ledger scanner starting mode=full_ledger chunk_size=%d workers=%d queue_limit=%d",
|
||||
aptosLedgerChunkSize,
|
||||
aptosLedgerWorkerCount,
|
||||
aptosLedgerQueueLimit,
|
||||
)
|
||||
for {
|
||||
if err := runAptosLedgerScanner(context.Background(), provider, cursor); err != nil {
|
||||
log.Sugar.Warnf("[APTOS] scanner stopped: %v", err)
|
||||
sleepOrDone(context.Background(), moveScannerRetryDelay)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func runAptosLedgerScanner(ctx context.Context, provider aptosChainProvider, cursor *aptosRuntimeCursor) error {
|
||||
if cursor == nil {
|
||||
cursor = &aptosRuntimeCursor{}
|
||||
}
|
||||
watchSignature := ""
|
||||
for {
|
||||
chain, interval := moveChainConfig(mdb.NetworkAptos)
|
||||
if chain == nil || !chain.Enabled {
|
||||
log.Sugar.Debug("[APTOS] chain disabled or not configured, idling")
|
||||
if !sleepOrDone(ctx, interval) {
|
||||
return nil
|
||||
}
|
||||
continue
|
||||
}
|
||||
state, err := loadMoveWatchState(mdb.NetworkAptos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(state.wallets) == 0 || len(state.tokens) == 0 {
|
||||
log.Sugar.Debug("[APTOS] no enabled wallets or tokens, idling")
|
||||
if !sleepOrDone(ctx, interval) {
|
||||
return nil
|
||||
}
|
||||
continue
|
||||
}
|
||||
if sig := moveWatchStateSignature(state); sig != watchSignature {
|
||||
watchSignature = sig
|
||||
log.Sugar.Infof("[APTOS] ledger scanner watching wallets=%d tokens=%d", len(state.wallets), len(state.tokens))
|
||||
}
|
||||
|
||||
latest, err := provider.LatestLedgerVersion()
|
||||
if err != nil {
|
||||
return fmt.Errorf("get latest ledger version: %w", err)
|
||||
}
|
||||
data.RecordRpcBlockHeight(mdb.NetworkAptos, latest)
|
||||
confirmed := moveConfirmedCursor(latest, chain.MinConfirmations)
|
||||
if !cursor.initialized {
|
||||
cursor.initialized = true
|
||||
cursor.lastSeenVersion = confirmed - aptosLedgerChunkSize
|
||||
if cursor.lastSeenVersion < -1 {
|
||||
cursor.lastSeenVersion = -1
|
||||
}
|
||||
log.Sugar.Infof(
|
||||
"[APTOS] initialized ledger cursor at version=%d confirmed_head=%d chunk_size=%d mode=full_ledger_watch",
|
||||
cursor.lastSeenVersion,
|
||||
confirmed,
|
||||
aptosLedgerChunkSize,
|
||||
)
|
||||
}
|
||||
|
||||
catchup, err := processAptosLedgerRound(ctx, provider, state, cursor, confirmed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !sleepOrDone(ctx, chooseAptosIdleDelay(catchup, interval)) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func processAptosLedgerRound(ctx context.Context, provider aptosChainProvider, state moveWatchState, cursor *aptosRuntimeCursor, confirmedVersion int64) (bool, error) {
|
||||
ranges := buildAptosLedgerRanges(cursor.lastSeenVersion, confirmedVersion, state)
|
||||
if len(ranges) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
lastRange := ranges[len(ranges)-1]
|
||||
log.Sugar.Infof(
|
||||
"[APTOS] queued ledger ranges count=%d from=%d to=%d cursor=%d confirmed_head=%d workers=%d",
|
||||
len(ranges),
|
||||
ranges[0].start,
|
||||
lastRange.start+lastRange.limit-1,
|
||||
cursor.lastSeenVersion,
|
||||
confirmedVersion,
|
||||
aptosLedgerWorkerCount,
|
||||
)
|
||||
|
||||
type rangeResult struct {
|
||||
item aptosLedgerRange
|
||||
err error
|
||||
}
|
||||
jobs := make(chan aptosLedgerRange, len(ranges))
|
||||
results := make(chan rangeResult, len(ranges))
|
||||
workers := aptosLedgerWorkerCount
|
||||
if len(ranges) < workers {
|
||||
workers = len(ranges)
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < workers; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for item := range jobs {
|
||||
results <- rangeResult{item: item, err: processAptosLedgerRange(ctx, provider, item, confirmedVersion)}
|
||||
}
|
||||
}()
|
||||
}
|
||||
for _, item := range ranges {
|
||||
jobs <- item
|
||||
}
|
||||
close(jobs)
|
||||
wg.Wait()
|
||||
close(results)
|
||||
|
||||
errByStart := make(map[int64]error, len(ranges))
|
||||
for result := range results {
|
||||
if result.err != nil {
|
||||
errByStart[result.item.start] = result.err
|
||||
}
|
||||
}
|
||||
|
||||
for _, item := range ranges {
|
||||
if err := errByStart[item.start]; err != nil {
|
||||
return cursor.lastSeenVersion < confirmedVersion, err
|
||||
}
|
||||
cursor.lastSeenVersion = item.start + item.limit - 1
|
||||
}
|
||||
return cursor.lastSeenVersion < confirmedVersion, nil
|
||||
}
|
||||
|
||||
func buildAptosLedgerRanges(lastSeenVersion int64, confirmedVersion int64, state moveWatchState) []aptosLedgerRange {
|
||||
if confirmedVersion <= lastSeenVersion {
|
||||
return nil
|
||||
}
|
||||
ranges := make([]aptosLedgerRange, 0, aptosLedgerQueueLimit)
|
||||
next := lastSeenVersion + 1
|
||||
if next < 0 {
|
||||
next = 0
|
||||
}
|
||||
for next <= confirmedVersion && len(ranges) < aptosLedgerQueueLimit {
|
||||
limit := confirmedVersion - next + 1
|
||||
if limit > aptosLedgerChunkSize {
|
||||
limit = aptosLedgerChunkSize
|
||||
}
|
||||
ranges = append(ranges, aptosLedgerRange{start: next, limit: limit, state: state})
|
||||
next += limit
|
||||
}
|
||||
return ranges
|
||||
}
|
||||
|
||||
func processAptosLedgerRange(ctx context.Context, provider aptosChainProvider, item aptosLedgerRange, confirmedVersion int64) error {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
body, err := provider.Transactions(item.start, item.limit)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get transactions start=%d limit=%d: %w", item.start, item.limit, err)
|
||||
}
|
||||
transfers, err := parseAptosTransfersForWallets(body, item.state.wallets, item.state.tokens)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse transactions start=%d limit=%d: %w", item.start, item.limit, err)
|
||||
}
|
||||
for _, transfer := range transfers {
|
||||
log.Sugar.Infof(
|
||||
"[APTOS] observed transfer version=%d tx=%s token=%s amount=%.8f raw=%s decimals=%d receive=%s key=%s",
|
||||
transfer.Version,
|
||||
transfer.TxID,
|
||||
transfer.Token,
|
||||
transfer.Amount,
|
||||
transfer.RawAmount.String(),
|
||||
transfer.Decimals,
|
||||
transfer.ReceiveAddress,
|
||||
transfer.TransferKey,
|
||||
)
|
||||
if err = processAptosObservedTransferFunc(transfer); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(transfers) > 0 {
|
||||
log.Sugar.Infof(
|
||||
"[APTOS] ledger range processed start=%d limit=%d to=%d transfers=%d confirmed_head=%d",
|
||||
item.start,
|
||||
item.limit,
|
||||
item.start+item.limit-1,
|
||||
len(transfers),
|
||||
confirmedVersion,
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func chooseAptosIdleDelay(catchup bool, interval time.Duration) time.Duration {
|
||||
if catchup {
|
||||
return moveCatchupYield
|
||||
}
|
||||
return interval
|
||||
}
|
||||
@@ -0,0 +1,440 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/GMWalletApp/epusdt/internal/testutil"
|
||||
"github.com/GMWalletApp/epusdt/model/dao"
|
||||
"github.com/GMWalletApp/epusdt/model/data"
|
||||
"github.com/GMWalletApp/epusdt/model/mdb"
|
||||
"github.com/GMWalletApp/epusdt/model/service"
|
||||
addressutil "github.com/GMWalletApp/epusdt/util/address"
|
||||
)
|
||||
|
||||
type fakeAptosProvider struct {
|
||||
mu sync.Mutex
|
||||
|
||||
latest int64
|
||||
|
||||
bodiesByStart map[int64][]byte
|
||||
errByStart map[int64]error
|
||||
|
||||
calls []aptosRangeCall
|
||||
}
|
||||
|
||||
type aptosRangeCall struct {
|
||||
start int64
|
||||
limit int64
|
||||
}
|
||||
|
||||
func (f *fakeAptosProvider) LatestLedgerVersion() (int64, error) {
|
||||
return f.latest, nil
|
||||
}
|
||||
|
||||
func (f *fakeAptosProvider) Transactions(start int64, limit int64) ([]byte, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
f.calls = append(f.calls, aptosRangeCall{start: start, limit: limit})
|
||||
if err := f.errByStart[start]; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if body := f.bodiesByStart[start]; len(body) > 0 {
|
||||
return append([]byte(nil), body...), nil
|
||||
}
|
||||
return []byte("[]"), nil
|
||||
}
|
||||
|
||||
func (f *fakeAptosProvider) rangeCalls() []aptosRangeCall {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
calls := append([]aptosRangeCall(nil), f.calls...)
|
||||
sort.Slice(calls, func(i, j int) bool {
|
||||
if calls[i].start == calls[j].start {
|
||||
return calls[i].limit < calls[j].limit
|
||||
}
|
||||
return calls[i].start < calls[j].start
|
||||
})
|
||||
return calls
|
||||
}
|
||||
|
||||
func resetAptosScannerHooks(t *testing.T) {
|
||||
t.Helper()
|
||||
oldParse := parseAptosTransfersForWallets
|
||||
oldProcess := processAptosObservedTransferFunc
|
||||
t.Cleanup(func() {
|
||||
parseAptosTransfersForWallets = oldParse
|
||||
processAptosObservedTransferFunc = oldProcess
|
||||
})
|
||||
}
|
||||
|
||||
func seedAptosScannerChain(t *testing.T, wallets ...string) []string {
|
||||
t.Helper()
|
||||
if err := dao.Mdb.Create(&mdb.Chain{Network: mdb.NetworkAptos, Enabled: true, MinConfirmations: 1, ScanIntervalSec: 1}).Error; err != nil {
|
||||
t.Fatalf("seed Aptos chain: %v", err)
|
||||
}
|
||||
if err := dao.Mdb.Create(&[]mdb.ChainToken{
|
||||
{Network: mdb.NetworkAptos, Symbol: "USDC", ContractAddress: "0xbae207659db88bea0cbead6da0ed00aac12edcdda169e591cd41c94180b46f3b", Decimals: 6, Enabled: true},
|
||||
{Network: mdb.NetworkAptos, Symbol: "USDT", ContractAddress: "0x357b0b74bc833e95a115ad22604854d6b0fca151cecd94111770e5d6ffc9dc2b", Decimals: 6, Enabled: true},
|
||||
{Network: mdb.NetworkAptos, Symbol: "MOVEUSD", ContractAddress: "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", Decimals: 6, Enabled: true},
|
||||
}).Error; err != nil {
|
||||
t.Fatalf("seed Aptos tokens: %v", err)
|
||||
}
|
||||
if len(wallets) == 0 {
|
||||
wallets = []string{"0xa"}
|
||||
}
|
||||
out := make([]string, 0, len(wallets))
|
||||
for _, wallet := range wallets {
|
||||
receive, err := addressutil.NormalizeMoveAddress(wallet)
|
||||
if err != nil {
|
||||
t.Fatalf("normalize Aptos address %q: %v", wallet, err)
|
||||
}
|
||||
if _, err = data.AddWalletAddressWithNetwork(mdb.NetworkAptos, receive); err != nil {
|
||||
t.Fatalf("seed Aptos wallet %s: %v", receive, err)
|
||||
}
|
||||
out = append(out, receive)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func TestProcessAptosLedgerRangePassesConfiguredTokens(t *testing.T) {
|
||||
cleanup := testutil.SetupTestDatabases(t)
|
||||
defer cleanup()
|
||||
resetAptosScannerHooks(t)
|
||||
seedAptosScannerChain(t, "0xa")
|
||||
|
||||
state, err := loadMoveWatchState(mdb.NetworkAptos)
|
||||
if err != nil {
|
||||
t.Fatalf("load state: %v", err)
|
||||
}
|
||||
var gotSymbols []string
|
||||
parseAptosTransfersForWallets = func(_ []byte, _ map[string]struct{}, tokens []mdb.ChainToken) ([]service.MoveObservedTransfer, error) {
|
||||
for _, token := range tokens {
|
||||
gotSymbols = append(gotSymbols, token.Symbol)
|
||||
}
|
||||
sort.Strings(gotSymbols)
|
||||
return nil, nil
|
||||
}
|
||||
provider := &fakeAptosProvider{bodiesByStart: map[int64][]byte{100: []byte("[]")}}
|
||||
|
||||
err = processAptosLedgerRange(context.Background(), provider, aptosLedgerRange{start: 100, limit: 1, state: state}, 100)
|
||||
if err != nil {
|
||||
t.Fatalf("processAptosLedgerRange(): %v", err)
|
||||
}
|
||||
want := []string{"MOVEUSD", "USDC", "USDT"}
|
||||
if len(gotSymbols) != len(want) {
|
||||
t.Fatalf("tokens = %#v, want %#v", gotSymbols, want)
|
||||
}
|
||||
for i := range want {
|
||||
if gotSymbols[i] != want[i] {
|
||||
t.Fatalf("tokens = %#v, want %#v", gotSymbols, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunAptosLedgerScannerIdlesWhenChainDisabled(t *testing.T) {
|
||||
cleanup := testutil.SetupTestDatabases(t)
|
||||
defer cleanup()
|
||||
resetAptosScannerHooks(t)
|
||||
if err := dao.Mdb.Create(&mdb.Chain{Network: mdb.NetworkAptos, Enabled: false, MinConfirmations: 1, ScanIntervalSec: 1}).Error; err != nil {
|
||||
t.Fatalf("seed disabled Aptos chain: %v", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
provider := &fakeAptosProvider{latest: 999}
|
||||
cursor := &aptosRuntimeCursor{}
|
||||
if err := runAptosLedgerScanner(ctx, provider, cursor); err != nil {
|
||||
t.Fatalf("runAptosLedgerScanner(): %v", err)
|
||||
}
|
||||
if cursor.initialized {
|
||||
t.Fatalf("cursor initialized while chain disabled: %#v", cursor)
|
||||
}
|
||||
if calls := provider.rangeCalls(); len(calls) != 0 {
|
||||
t.Fatalf("range calls = %#v, want none", calls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunAptosLedgerScannerInitializesAtTrailingConfirmedChunk(t *testing.T) {
|
||||
cleanup := testutil.SetupTestDatabases(t)
|
||||
defer cleanup()
|
||||
resetAptosScannerHooks(t)
|
||||
seedAptosScannerChain(t, "0xa")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
provider := &fakeAptosProvider{latest: 999}
|
||||
cursor := &aptosRuntimeCursor{}
|
||||
if err := runAptosLedgerScanner(ctx, provider, cursor); err != nil {
|
||||
t.Fatalf("runAptosLedgerScanner(): %v", err)
|
||||
}
|
||||
|
||||
if !cursor.initialized || cursor.lastSeenVersion != 999 {
|
||||
t.Fatalf("cursor = %#v, want initialized and advanced to confirmed head 999", cursor)
|
||||
}
|
||||
calls := provider.rangeCalls()
|
||||
if len(calls) != 1 || calls[0].start != 900 || calls[0].limit != 100 {
|
||||
t.Fatalf("range calls = %#v, want start=900 limit=100", calls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessAptosLedgerRoundDoesNotAdvanceUnconfirmedVersion(t *testing.T) {
|
||||
cleanup := testutil.SetupTestDatabases(t)
|
||||
defer cleanup()
|
||||
resetAptosScannerHooks(t)
|
||||
seedAptosScannerChain(t, "0xa")
|
||||
|
||||
provider := &fakeAptosProvider{latest: 100}
|
||||
cursor := &aptosRuntimeCursor{initialized: true, lastSeenVersion: 99}
|
||||
state, err := loadMoveWatchState(mdb.NetworkAptos)
|
||||
if err != nil {
|
||||
t.Fatalf("load state: %v", err)
|
||||
}
|
||||
|
||||
catchup, err := processAptosLedgerRound(context.Background(), provider, state, cursor, 99)
|
||||
if err != nil {
|
||||
t.Fatalf("processAptosLedgerRound(): %v", err)
|
||||
}
|
||||
if catchup {
|
||||
t.Fatal("catchup = true, want false")
|
||||
}
|
||||
if cursor.lastSeenVersion != 99 {
|
||||
t.Fatalf("lastSeenVersion = %d, want 99", cursor.lastSeenVersion)
|
||||
}
|
||||
if calls := provider.rangeCalls(); len(calls) != 0 {
|
||||
t.Fatalf("range calls = %#v, want none", calls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessAptosLedgerRoundAdvancesAfterSuccessfulRange(t *testing.T) {
|
||||
cleanup := testutil.SetupTestDatabases(t)
|
||||
defer cleanup()
|
||||
resetAptosScannerHooks(t)
|
||||
seedAptosScannerChain(t, "0xa")
|
||||
|
||||
state, err := loadMoveWatchState(mdb.NetworkAptos)
|
||||
if err != nil {
|
||||
t.Fatalf("load state: %v", err)
|
||||
}
|
||||
provider := &fakeAptosProvider{bodiesByStart: map[int64][]byte{100: []byte("[]")}}
|
||||
cursor := &aptosRuntimeCursor{initialized: true, lastSeenVersion: 99}
|
||||
|
||||
catchup, err := processAptosLedgerRound(context.Background(), provider, state, cursor, 250)
|
||||
if err != nil {
|
||||
t.Fatalf("processAptosLedgerRound(): %v", err)
|
||||
}
|
||||
if catchup {
|
||||
t.Fatal("catchup = true, want false")
|
||||
}
|
||||
if cursor.lastSeenVersion != 250 {
|
||||
t.Fatalf("lastSeenVersion = %d, want 250", cursor.lastSeenVersion)
|
||||
}
|
||||
calls := provider.rangeCalls()
|
||||
if len(calls) != 2 || calls[0].start != 100 || calls[0].limit != 100 || calls[1].start != 200 || calls[1].limit != 51 {
|
||||
t.Fatalf("range calls = %#v, want [100/100 200/51]", calls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessAptosLedgerRoundDoesNotAdvanceOnRangeFailure(t *testing.T) {
|
||||
cleanup := testutil.SetupTestDatabases(t)
|
||||
defer cleanup()
|
||||
resetAptosScannerHooks(t)
|
||||
seedAptosScannerChain(t, "0xa")
|
||||
|
||||
state, err := loadMoveWatchState(mdb.NetworkAptos)
|
||||
if err != nil {
|
||||
t.Fatalf("load state: %v", err)
|
||||
}
|
||||
provider := &fakeAptosProvider{errByStart: map[int64]error{100: errors.New("temporary rpc failure")}}
|
||||
cursor := &aptosRuntimeCursor{initialized: true, lastSeenVersion: 99}
|
||||
|
||||
if _, err = processAptosLedgerRound(context.Background(), provider, state, cursor, 150); err == nil {
|
||||
t.Fatal("processAptosLedgerRound() error = nil, want error")
|
||||
}
|
||||
if cursor.lastSeenVersion != 99 {
|
||||
t.Fatalf("lastSeenVersion = %d, want 99", cursor.lastSeenVersion)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessAptosLedgerRoundProcessesMultipleChunks(t *testing.T) {
|
||||
cleanup := testutil.SetupTestDatabases(t)
|
||||
defer cleanup()
|
||||
resetAptosScannerHooks(t)
|
||||
seedAptosScannerChain(t, "0xa")
|
||||
|
||||
state, err := loadMoveWatchState(mdb.NetworkAptos)
|
||||
if err != nil {
|
||||
t.Fatalf("load state: %v", err)
|
||||
}
|
||||
provider := &fakeAptosProvider{
|
||||
bodiesByStart: map[int64][]byte{
|
||||
100: []byte("[]"),
|
||||
200: []byte("[]"),
|
||||
},
|
||||
}
|
||||
cursor := &aptosRuntimeCursor{initialized: true, lastSeenVersion: 99}
|
||||
|
||||
catchup, err := processAptosLedgerRound(context.Background(), provider, state, cursor, 250)
|
||||
if err != nil {
|
||||
t.Fatalf("processAptosLedgerRound(): %v", err)
|
||||
}
|
||||
if catchup {
|
||||
t.Fatal("catchup = true, want false")
|
||||
}
|
||||
if cursor.lastSeenVersion != 250 {
|
||||
t.Fatalf("lastSeenVersion = %d, want 250", cursor.lastSeenVersion)
|
||||
}
|
||||
calls := provider.rangeCalls()
|
||||
if len(calls) != 2 || calls[0].start != 100 || calls[0].limit != 100 || calls[1].start != 200 || calls[1].limit != 51 {
|
||||
t.Fatalf("range calls = %#v, want [100/100 200/51]", calls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildAptosLedgerRangesStopsAtQueueLimit(t *testing.T) {
|
||||
state := moveWatchState{}
|
||||
ranges := buildAptosLedgerRanges(0, aptosLedgerChunkSize*aptosLedgerQueueLimit+500, state)
|
||||
if len(ranges) != aptosLedgerQueueLimit {
|
||||
t.Fatalf("ranges len = %d, want %d", len(ranges), aptosLedgerQueueLimit)
|
||||
}
|
||||
first := ranges[0]
|
||||
last := ranges[len(ranges)-1]
|
||||
if first.start != 1 || first.limit != aptosLedgerChunkSize {
|
||||
t.Fatalf("first range = %#v, want start=1 limit=%d", first, aptosLedgerChunkSize)
|
||||
}
|
||||
if last.start != 1+aptosLedgerChunkSize*int64(aptosLedgerQueueLimit-1) || last.limit != aptosLedgerChunkSize {
|
||||
t.Fatalf("last range = %#v", last)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessAptosLedgerRoundMarksMatchingUSDTOrderPaid(t *testing.T) {
|
||||
cleanup := testutil.SetupTestDatabases(t)
|
||||
defer cleanup()
|
||||
resetAptosScannerHooks(t)
|
||||
wallets := seedAptosScannerChain(t, "0xa")
|
||||
receive := wallets[0]
|
||||
amount := 3.1
|
||||
tradeID := "aptos_trade_1"
|
||||
usdt := "0x357b0b74bc833e95a115ad22604854d6b0fca151cecd94111770e5d6ffc9dc2b"
|
||||
body := aptosFungibleTransferBody(t, "0xabc", 101, receive, usdt, "3100000")
|
||||
|
||||
order := &mdb.Orders{
|
||||
TradeId: tradeID,
|
||||
OrderId: "aptos_order_1",
|
||||
Amount: amount,
|
||||
ActualAmount: amount,
|
||||
ReceiveAddress: receive,
|
||||
Token: "USDT",
|
||||
Network: mdb.NetworkAptos,
|
||||
Status: mdb.StatusWaitPay,
|
||||
CallBackConfirm: mdb.CallBackConfirmOk,
|
||||
PaymentType: mdb.PaymentTypeGmpay,
|
||||
PayProvider: mdb.PaymentProviderOnChain,
|
||||
}
|
||||
if err := dao.Mdb.Create(order).Error; err != nil {
|
||||
t.Fatalf("create order: %v", err)
|
||||
}
|
||||
if err := data.LockTransaction(mdb.NetworkAptos, receive, "USDT", tradeID, amount, time.Hour); err != nil {
|
||||
t.Fatalf("lock transaction: %v", err)
|
||||
}
|
||||
|
||||
state, err := loadMoveWatchState(mdb.NetworkAptos)
|
||||
if err != nil {
|
||||
t.Fatalf("load state: %v", err)
|
||||
}
|
||||
provider := &fakeAptosProvider{bodiesByStart: map[int64][]byte{101: body}}
|
||||
cursor := &aptosRuntimeCursor{initialized: true, lastSeenVersion: 100}
|
||||
|
||||
if _, err = processAptosLedgerRound(context.Background(), provider, state, cursor, 101); err != nil {
|
||||
t.Fatalf("processAptosLedgerRound(): %v", err)
|
||||
}
|
||||
paid, err := data.GetOrderInfoByTradeId(tradeID)
|
||||
if err != nil {
|
||||
t.Fatalf("reload order: %v", err)
|
||||
}
|
||||
if paid.Status != mdb.StatusPaySuccess || paid.CallBackConfirm != mdb.CallBackConfirmNo || paid.BlockTransactionId != "0xabc" {
|
||||
t.Fatalf("paid order = %#v", paid)
|
||||
}
|
||||
lockTradeID, err := data.GetTradeIdByWalletAddressAndAmountAndToken(mdb.NetworkAptos, receive, "USDT", amount)
|
||||
if err != nil {
|
||||
t.Fatalf("lookup lock: %v", err)
|
||||
}
|
||||
if lockTradeID != "" {
|
||||
t.Fatalf("transaction lock still exists: %s", lockTradeID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessAptosLedgerRoundDoesNotReprocessDuplicateTransferKey(t *testing.T) {
|
||||
cleanup := testutil.SetupTestDatabases(t)
|
||||
defer cleanup()
|
||||
resetAptosScannerHooks(t)
|
||||
wallets := seedAptosScannerChain(t, "0xa")
|
||||
receive := wallets[0]
|
||||
usdc := "0xbae207659db88bea0cbead6da0ed00aac12edcdda169e591cd41c94180b46f3b"
|
||||
|
||||
state, err := loadMoveWatchState(mdb.NetworkAptos)
|
||||
if err != nil {
|
||||
t.Fatalf("load state: %v", err)
|
||||
}
|
||||
provider := &fakeAptosProvider{
|
||||
bodiesByStart: map[int64][]byte{
|
||||
101: aptosFungibleTransferBody(t, "0xabc", 101, receive, usdc, "1200000"),
|
||||
102: aptosFungibleTransferBody(t, "0xabc", 101, receive, usdc, "1200000"),
|
||||
},
|
||||
}
|
||||
var processed int
|
||||
processAptosObservedTransferFunc = func(transfer service.MoveObservedTransfer) error {
|
||||
processed++
|
||||
return service.ProcessMoveObservedTransferResult(transfer)
|
||||
}
|
||||
cursor := &aptosRuntimeCursor{initialized: true, lastSeenVersion: 100}
|
||||
|
||||
if _, err = processAptosLedgerRound(context.Background(), provider, state, cursor, 101); err != nil {
|
||||
t.Fatalf("first processAptosLedgerRound(): %v", err)
|
||||
}
|
||||
if _, err = processAptosLedgerRound(context.Background(), provider, state, cursor, 102); err != nil {
|
||||
t.Fatalf("second processAptosLedgerRound(): %v", err)
|
||||
}
|
||||
if processed != 2 {
|
||||
t.Fatalf("scanner processed callback count = %d, want 2", processed)
|
||||
}
|
||||
if cursor.lastSeenVersion != 102 {
|
||||
t.Fatalf("lastSeenVersion = %d, want 102", cursor.lastSeenVersion)
|
||||
}
|
||||
}
|
||||
|
||||
func aptosFungibleTransferBody(t *testing.T, hash string, version int64, receive string, metadata string, rawAmount string) []byte {
|
||||
t.Helper()
|
||||
store, err := addressutil.NormalizeMoveAddress(fmt.Sprintf("0x%x", version+1000))
|
||||
if err != nil {
|
||||
t.Fatalf("normalize store: %v", err)
|
||||
}
|
||||
senderStore, err := addressutil.NormalizeMoveAddress(fmt.Sprintf("0x%x", version+2000))
|
||||
if err != nil {
|
||||
t.Fatalf("normalize sender store: %v", err)
|
||||
}
|
||||
return []byte(`[
|
||||
{
|
||||
"type":"user_transaction",
|
||||
"success":true,
|
||||
"hash":"` + hash + `",
|
||||
"version":"` + fmt.Sprintf("%d", version) + `",
|
||||
"timestamp":"` + fmt.Sprintf("%d", time.Now().UnixMicro()) + `",
|
||||
"events":[
|
||||
{"type":"0x1::fungible_asset::Withdraw","data":{"amount":"` + rawAmount + `","store":"` + senderStore + `"}},
|
||||
{"type":"0x1::fungible_asset::Deposit","data":{"amount":"` + rawAmount + `","store":"` + store + `"}}
|
||||
],
|
||||
"changes":[
|
||||
{"address":"` + senderStore + `","data":{"type":"0x1::fungible_asset::FungibleStore","data":{"metadata":{"inner":"` + metadata + `"}}},"type":"write_resource"},
|
||||
{"address":"` + store + `","data":{"type":"0x1::fungible_asset::FungibleStore","data":{"metadata":{"inner":"` + metadata + `"}}},"type":"write_resource"},
|
||||
{"address":"` + store + `","data":{"type":"0x1::object::ObjectCore","data":{"owner":"` + receive + `"}},"type":"write_resource"}
|
||||
]
|
||||
}
|
||||
]`)
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/GMWalletApp/epusdt/model/data"
|
||||
"github.com/GMWalletApp/epusdt/model/mdb"
|
||||
addressutil "github.com/GMWalletApp/epusdt/util/address"
|
||||
"github.com/GMWalletApp/epusdt/util/log"
|
||||
)
|
||||
|
||||
const (
|
||||
moveScannerRetryDelay = 3 * time.Second
|
||||
moveCatchupYield = 250 * time.Millisecond
|
||||
)
|
||||
|
||||
type moveWatchState struct {
|
||||
wallets map[string]struct{}
|
||||
tokens []mdb.ChainToken
|
||||
}
|
||||
|
||||
func moveChainConfig(network string) (*mdb.Chain, time.Duration) {
|
||||
row, err := data.GetChainByNetwork(network)
|
||||
if err != nil || row == nil || row.ID == 0 {
|
||||
if err != nil {
|
||||
log.Sugar.Warnf("[%s] load chain config failed: %v", strings.ToUpper(network), err)
|
||||
}
|
||||
return nil, 5 * time.Second
|
||||
}
|
||||
interval := time.Duration(row.ScanIntervalSec) * time.Second
|
||||
if interval <= 0 {
|
||||
interval = 5 * time.Second
|
||||
}
|
||||
return row, interval
|
||||
}
|
||||
|
||||
func loadMoveWatchState(network string) (moveWatchState, error) {
|
||||
rows, err := data.GetAvailableWalletAddressByNetwork(network)
|
||||
if err != nil {
|
||||
return moveWatchState{}, err
|
||||
}
|
||||
state := moveWatchState{wallets: make(map[string]struct{})}
|
||||
for _, row := range rows {
|
||||
address, err := addressutil.NormalizeMoveAddress(row.Address)
|
||||
if err != nil {
|
||||
log.Sugar.Warnf("[%s] skip invalid wallet address=%s err=%v", strings.ToUpper(network), row.Address, err)
|
||||
continue
|
||||
}
|
||||
state.wallets[address] = struct{}{}
|
||||
}
|
||||
tokens, err := data.ListEnabledChainTokensByNetwork(network)
|
||||
if err != nil {
|
||||
return moveWatchState{}, err
|
||||
}
|
||||
state.tokens = tokens
|
||||
return state, nil
|
||||
}
|
||||
|
||||
func moveWatchStateSignature(state moveWatchState) string {
|
||||
return fmt.Sprintf("wallets=%d/tokens=%d", len(state.wallets), len(state.tokens))
|
||||
}
|
||||
|
||||
func moveConfirmedCursor(latest int64, minConfirmations int) int64 {
|
||||
if latest <= 0 {
|
||||
return 0
|
||||
}
|
||||
if minConfirmations <= 1 {
|
||||
return latest
|
||||
}
|
||||
confirmed := latest - int64(minConfirmations) + 1
|
||||
if confirmed < 0 {
|
||||
return 0
|
||||
}
|
||||
return confirmed
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package addressutil
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NormalizeMoveAddress returns the canonical 0x-prefixed, 64-hex-character
|
||||
// account address used by Aptos.
|
||||
func NormalizeMoveAddress(input string) (string, error) {
|
||||
raw := strings.ToLower(strings.TrimSpace(input))
|
||||
raw = strings.TrimPrefix(raw, "0x")
|
||||
if raw == "" {
|
||||
return "", fmt.Errorf("move address is empty")
|
||||
}
|
||||
if len(raw) > 64 {
|
||||
return "", fmt.Errorf("move address is too long")
|
||||
}
|
||||
if _, err := hex.DecodeString(padEvenHex(raw)); err != nil {
|
||||
return "", fmt.Errorf("invalid move address")
|
||||
}
|
||||
return "0x" + strings.Repeat("0", 64-len(raw)) + raw, nil
|
||||
}
|
||||
|
||||
func padEvenHex(raw string) string {
|
||||
if len(raw)%2 == 0 {
|
||||
return raw
|
||||
}
|
||||
return "0" + raw
|
||||
}
|
||||
|
||||
// NormalizeMoveAssetID canonicalizes Move asset identifiers used in
|
||||
// chain_tokens.contract_address for Aptos. A plain address is normalized to
|
||||
// 64 hex chars; structured types keep module/name suffixes after normalizing
|
||||
// the leading address.
|
||||
func NormalizeMoveAssetID(input string) string {
|
||||
raw := strings.ToLower(strings.TrimSpace(input))
|
||||
if raw == "" {
|
||||
return ""
|
||||
}
|
||||
parts := strings.Split(raw, "::")
|
||||
if len(parts) == 1 {
|
||||
if addr, err := NormalizeMoveAddress(raw); err == nil {
|
||||
return addr
|
||||
}
|
||||
return raw
|
||||
}
|
||||
if addr, err := NormalizeMoveAddress(parts[0]); err == nil {
|
||||
parts[0] = addr
|
||||
}
|
||||
return strings.Join(parts, "::")
|
||||
}
|
||||
|
||||
func MoveAssetMatches(observed, configured string) bool {
|
||||
observed = NormalizeMoveAssetID(observed)
|
||||
configured = NormalizeMoveAssetID(configured)
|
||||
if configured == "" {
|
||||
return observed == ""
|
||||
}
|
||||
if observed == configured {
|
||||
return true
|
||||
}
|
||||
// Aptos FA event payloads may expose only the metadata address while other
|
||||
// payloads expose a type string containing the same address.
|
||||
return strings.Contains(observed, configured)
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package addressutil
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNormalizeMoveAddress(t *testing.T) {
|
||||
got, err := NormalizeMoveAddress(" A ")
|
||||
if err != nil {
|
||||
t.Fatalf("NormalizeMoveAddress: %v", err)
|
||||
}
|
||||
want := "0x000000000000000000000000000000000000000000000000000000000000000a"
|
||||
if got != want {
|
||||
t.Fatalf("address = %q, want %q", got, want)
|
||||
}
|
||||
|
||||
got, err = NormalizeMoveAddress("0X" + "ABCDEF")
|
||||
if err != nil {
|
||||
t.Fatalf("NormalizeMoveAddress uppercase: %v", err)
|
||||
}
|
||||
want = "0x0000000000000000000000000000000000000000000000000000000000abcdef"
|
||||
if got != want {
|
||||
t.Fatalf("uppercase address = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeMoveAddressRejectsInvalid(t *testing.T) {
|
||||
for _, input := range []string{"", "0xzz", "0x" + strings.Repeat("1", 65)} {
|
||||
if _, err := NormalizeMoveAddress(input); err == nil {
|
||||
t.Fatalf("NormalizeMoveAddress(%q) succeeded, want error", input)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeMoveAssetIDNormalizesLeadingAddress(t *testing.T) {
|
||||
got := NormalizeMoveAssetID("0X1::FUNGIBLE_ASSET::METADATA")
|
||||
want := "0x0000000000000000000000000000000000000000000000000000000000000001::fungible_asset::metadata"
|
||||
if got != want {
|
||||
t.Fatalf("asset id = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,9 @@ var Errno = map[int]string{
|
||||
10041: "invalid notify url",
|
||||
10042: "payment provider order creation failed",
|
||||
10043: "invalid setting item",
|
||||
10044: "invalid order redirect url",
|
||||
10045: "order api key unavailable",
|
||||
10046: "failed to build epay return signature",
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -94,6 +97,9 @@ var (
|
||||
NotifyURLErr = Err(10041)
|
||||
PaymentProviderCreateErr = Err(10042)
|
||||
SettingItemErr = Err(10043)
|
||||
OrderRedirectURLErr = Err(10044)
|
||||
OrderApiKeyUnavailableErr = Err(10045)
|
||||
EPayReturnSignatureErr = Err(10046)
|
||||
)
|
||||
|
||||
type RspError struct {
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
import{t as e}from"./unauthorized-error-CASnS-6J.js";var t=e;export{t as component};
|
||||
@@ -0,0 +1 @@
|
||||
import{t as e}from"./unauthorized-error-B6bKLdq-.js";var t=e;export{t as component};
|
||||
@@ -1 +0,0 @@
|
||||
import{t as e}from"./forbidden-peSSCojP.js";var t=e;export{t as component};
|
||||
@@ -0,0 +1 @@
|
||||
import{t as e}from"./forbidden-DZPSchCU.js";var t=e;export{t as component};
|
||||
@@ -1 +0,0 @@
|
||||
import{t as e}from"./not-found-error-CTygh2Q3.js";var t=e;export{t as component};
|
||||
@@ -0,0 +1 @@
|
||||
import{t as e}from"./not-found-error-CHHM1I1c.js";var t=e;export{t as component};
|
||||
@@ -1 +0,0 @@
|
||||
import{t as e}from"./general-error-CDe5w_m4.js";var t=e;export{t as component};
|
||||
@@ -0,0 +1 @@
|
||||
import{t as e}from"./general-error-BoIfl_4Z.js";var t=e;export{t as component};
|
||||
@@ -1 +0,0 @@
|
||||
import{t as e}from"./maintenance-error-C9XjssOV.js";var t=e;export{t as component};
|
||||
@@ -0,0 +1 @@
|
||||
import{t as e}from"./maintenance-error-CuCw7L8t.js";var t=e;export{t as component};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
import{r as e}from"./chunk-DECur_0Z.js";import{t}from"./react-CO2uhaBc.js";import{Zp as n}from"./messages-BLwDcEAr.js";import{o as r,s as i,t as a}from"./useRouter-DtTm7XkK.js";import{t as o}from"./useStore-CupyIEEP.js";import{f as s}from"./ClientOnly-CWlB586P.js";import"./redirect-DMNGvjzO.js";import{a as c,i as l,r as u,s as d,t as f}from"./Match-CeXMJwDZ.js";import{n as p}from"./matchContext-BFCdcHzo.js";import{t as m}from"./atom-DQpaPCwp.js";function h(e){if(typeof document<`u`&&document.querySelector){let t=e.stores.location.get(),n=t.state.__hashScrollIntoViewOptions??!0;if(n&&t.hash!==``){let e=document.getElementById(t.hash);e&&e.scrollIntoView(n)}}}var g=e(t(),1);function _(){let e=a(),t=g.useRef({router:e,mounted:!1}),[n,c]=g.useState(!1),l=o(e.stores.isLoading,e=>e),u=o(e.stores.hasPending,e=>e),f=i(l),p=l||n||u,_=i(p),v=l||u,y=i(v);return e.startTransition=e=>{c(!0),g.startTransition(()=>{e(),c(!1)})},g.useEffect(()=>{let t=e.history.subscribe(e.load),n=e.buildLocation({to:e.latestLocation.pathname,search:!0,params:!0,hash:!0,state:!0,_includeValidateSearch:!0});return s(e.latestLocation.publicHref)!==s(n.publicHref)&&e.commitLocation({...n,replace:!0}),()=>{t()}},[e,e.history]),r(()=>{typeof window<`u`&&e.ssr||t.current.router===e&&t.current.mounted||(t.current={router:e,mounted:!0},(async()=>{try{await e.load()}catch(e){console.error(e)}})())},[e]),r(()=>{f&&!l&&e.emit({type:`onLoad`,...d(e.stores.location.get(),e.stores.resolvedLocation.get())})},[f,e,l]),r(()=>{y&&!v&&e.emit({type:`onBeforeRouteMount`,...d(e.stores.location.get(),e.stores.resolvedLocation.get())})},[v,y,e]),r(()=>{if(_&&!p){let t=d(e.stores.location.get(),e.stores.resolvedLocation.get());e.emit({type:`onResolved`,...t}),m(()=>{e.stores.status.set(`idle`),e.stores.resolvedLocation.set(e.stores.location.get())}),t.hrefChanged&&h(e)}},[p,_,e]),null}var v=n();function y(){let e=a(),t=e.routesById.__root__.options.pendingComponent??e.options.defaultPendingComponent,n=t?(0,v.jsx)(t,{}):null,r=(0,v.jsxs)(typeof document<`u`&&e.ssr?u:g.Suspense,{fallback:n,children:[(0,v.jsx)(_,{}),(0,v.jsx)(b,{})]});return e.options.InnerWrap?(0,v.jsx)(e.options.InnerWrap,{children:r}):r}function b(){let e=a(),t=o(e.stores.firstId,e=>e),n=o(e.stores.loadedAt,e=>e),r=t?(0,v.jsx)(f,{matchId:t}):null;return(0,v.jsx)(p.Provider,{value:t,children:e.options.disableGlobalCatchBoundary?r:(0,v.jsx)(l,{getResetKey:()=>n,errorComponent:c,onCatch:void 0,children:r})})}function x(){let e=a();return o(e.stores.matchRouteDeps,e=>e),g.useCallback(t=>{let{pending:n,caseSensitive:r,fuzzy:i,includeSearch:a,...o}=t;return e.matchRoute(o,{pending:n,caseSensitive:r,fuzzy:i,includeSearch:a})},[e])}export{x as n,y as t};
|
||||
import{r as e}from"./chunk-DECur_0Z.js";import{t}from"./react-CO2uhaBc.js";import{em as n}from"./messages-Bp0bCjzp.js";import{o as r,s as i,t as a}from"./useRouter-DtTm7XkK.js";import{t as o}from"./useStore-CupyIEEP.js";import{f as s}from"./ClientOnly-fFIveJVd.js";import"./redirect-DMNGvjzO.js";import{a as c,i as l,r as u,s as d,t as f}from"./Match-Cm64cgS9.js";import{n as p}from"./matchContext-BFCdcHzo.js";import{t as m}from"./atom-DQpaPCwp.js";function h(e){if(typeof document<`u`&&document.querySelector){let t=e.stores.location.get(),n=t.state.__hashScrollIntoViewOptions??!0;if(n&&t.hash!==``){let e=document.getElementById(t.hash);e&&e.scrollIntoView(n)}}}var g=e(t(),1);function _(){let e=a(),t=g.useRef({router:e,mounted:!1}),[n,c]=g.useState(!1),l=o(e.stores.isLoading,e=>e),u=o(e.stores.hasPending,e=>e),f=i(l),p=l||n||u,_=i(p),v=l||u,y=i(v);return e.startTransition=e=>{c(!0),g.startTransition(()=>{e(),c(!1)})},g.useEffect(()=>{let t=e.history.subscribe(e.load),n=e.buildLocation({to:e.latestLocation.pathname,search:!0,params:!0,hash:!0,state:!0,_includeValidateSearch:!0});return s(e.latestLocation.publicHref)!==s(n.publicHref)&&e.commitLocation({...n,replace:!0}),()=>{t()}},[e,e.history]),r(()=>{typeof window<`u`&&e.ssr||t.current.router===e&&t.current.mounted||(t.current={router:e,mounted:!0},(async()=>{try{await e.load()}catch(e){console.error(e)}})())},[e]),r(()=>{f&&!l&&e.emit({type:`onLoad`,...d(e.stores.location.get(),e.stores.resolvedLocation.get())})},[f,e,l]),r(()=>{y&&!v&&e.emit({type:`onBeforeRouteMount`,...d(e.stores.location.get(),e.stores.resolvedLocation.get())})},[v,y,e]),r(()=>{if(_&&!p){let t=d(e.stores.location.get(),e.stores.resolvedLocation.get());e.emit({type:`onResolved`,...t}),m(()=>{e.stores.status.set(`idle`),e.stores.resolvedLocation.set(e.stores.location.get())}),t.hrefChanged&&h(e)}},[p,_,e]),null}var v=n();function y(){let e=a(),t=e.routesById.__root__.options.pendingComponent??e.options.defaultPendingComponent,n=t?(0,v.jsx)(t,{}):null,r=(0,v.jsxs)(typeof document<`u`&&e.ssr?u:g.Suspense,{fallback:n,children:[(0,v.jsx)(_,{}),(0,v.jsx)(b,{})]});return e.options.InnerWrap?(0,v.jsx)(e.options.InnerWrap,{children:r}):r}function b(){let e=a(),t=o(e.stores.firstId,e=>e),n=o(e.stores.loadedAt,e=>e),r=t?(0,v.jsx)(f,{matchId:t}):null;return(0,v.jsx)(p.Provider,{value:t,children:e.options.disableGlobalCatchBoundary?r:(0,v.jsx)(l,{getResetKey:()=>n,errorComponent:c,onCatch:void 0,children:r})})}function x(){let e=a();return o(e.stores.matchRouteDeps,e=>e),g.useCallback(t=>{let{pending:n,caseSensitive:r,fuzzy:i,includeSearch:a,...o}=t;return e.matchRoute(o,{pending:n,caseSensitive:r,fuzzy:i,includeSearch:a})},[e])}export{x as n,y as t};
|
||||
@@ -0,0 +1 @@
|
||||
import{em as e}from"./messages-Bp0bCjzp.js";import{m as t}from"./search-provider-Bl2HDfcG.js";import{n,t as r}from"./theme-switch-B3Qb32n8.js";import{t as i}from"./general-error-BoIfl_4Z.js";import{t as a}from"./not-found-error-CHHM1I1c.js";import{t as o}from"./_error-CeoUllM-.js";import{t as s}from"./unauthorized-error-B6bKLdq-.js";import{t as c}from"./forbidden-DZPSchCU.js";import{t as l}from"./maintenance-error-CuCw7L8t.js";import{n as u,r as d,t as f}from"./header-D45l3Ai7.js";var p=e();function m(){let{error:e}=o.useParams(),m={unauthorized:s,forbidden:c,"not-found":a,"internal-server-error":i,"maintenance-error":l}[e]||a;return(0,p.jsxs)(p.Fragment,{children:[(0,p.jsxs)(f,{className:`border-b`,fixed:!0,children:[(0,p.jsx)(u,{}),(0,p.jsxs)(`div`,{className:`ms-auto flex items-center space-x-4`,children:[(0,p.jsx)(n,{}),(0,p.jsx)(r,{}),(0,p.jsx)(t,{}),(0,p.jsx)(d,{})]})]}),(0,p.jsx)(`div`,{className:`flex-1 [&>div]:h-full`,children:(0,p.jsx)(m,{})})]})}export{m as component};
|
||||
@@ -0,0 +1,2 @@
|
||||
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/_error-BSLzv73v.js","assets/search-provider-Bl2HDfcG.js","assets/chunk-DECur_0Z.js","assets/dist-CRowTfGU.js","assets/dist-CKFLmh1A.js","assets/messages-Bp0bCjzp.js","assets/react-CO2uhaBc.js","assets/confirm-dialog-YmxNnOr3.js","assets/button-BgMnOYKD.js","assets/clsx-D9aGYY3V.js","assets/dist-CPQ-sRtL.js","assets/dist-_ND6L-P3.js","assets/dist-B0ikDpJU.js","assets/dist-BFnxq45V.js","assets/es2015-D8VLlhse.js","assets/dist-Clua1vrx.js","assets/dist-C97YBjnT.js","assets/dist-036CEgdV.js","assets/dist-D7AUoOWu.js","assets/dist-C6i4A_Js.js","assets/dist-BixeH3nX.js","assets/dist-DGDEBCW9.js","assets/separator-DbmFQXe4.js","assets/tooltip-Gx9CUpPD.js","assets/dist-BSXD7MjW.js","assets/dist-Bmn8KNt7.js","assets/auth-store-Csn6HPxJ.js","assets/dist-BGqHIBUe.js","assets/with-selector-DBfssCrY.js","assets/cookies-BzZOQYPw.js","assets/useRouter-DtTm7XkK.js","assets/useNavigate-7u4DX0Ho.js","assets/useStore-CupyIEEP.js","assets/command-CIv3ggHf.js","assets/createLucideIcon-Br0Bd5k2.js","assets/dialog-BJ5VA2v_.js","assets/skeleton-BQsmx80h.js","assets/wallet-Bgblr4kl.js","assets/sun-JU8p4FoE.js","assets/shield-check-MAPDL1u8.js","assets/logo-Ce__4GTc.js","assets/input-BleRUV2A.js","assets/font-provider-CtpKPVa7.js","assets/theme-provider-CyJJNAox.js","assets/theme-switch-B3Qb32n8.js","assets/dropdown-menu-8-hEBtzr.js","assets/check-CHp0nSkR.js","assets/header-D45l3Ai7.js","assets/link-BYG8nKNO.js","assets/ClientOnly-fFIveJVd.js","assets/forbidden-DZPSchCU.js","assets/general-error-BoIfl_4Z.js","assets/maintenance-error-CuCw7L8t.js","assets/not-found-error-CHHM1I1c.js","assets/unauthorized-error-B6bKLdq-.js"])))=>i.map(i=>d[i]);
|
||||
import{n as e,t}from"./lazyRouteComponent-iygd3-DM.js";import{t as n}from"./preload-helper-DoS0eHac.js";var r=e(`/_authenticated/errors/$error`)({component:t(()=>n(()=>import(`./_error-BSLzv73v.js`),__vite__mapDeps([0,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])),`component`)});export{r as t};
|
||||
@@ -1 +0,0 @@
|
||||
import{Zp as e}from"./messages-BLwDcEAr.js";import{m as t}from"./search-provider-B67mlhed.js";import{n,t as r}from"./theme-switch-B3KcLEky.js";import{t as i}from"./general-error-CDe5w_m4.js";import{t as a}from"./not-found-error-CTygh2Q3.js";import{t as o}from"./_error-wx-ubgoM.js";import{t as s}from"./unauthorized-error-CASnS-6J.js";import{t as c}from"./forbidden-peSSCojP.js";import{t as l}from"./maintenance-error-C9XjssOV.js";import{n as u,r as d,t as f}from"./header-NBnXYgM_.js";var p=e();function m(){let{error:e}=o.useParams(),m={unauthorized:s,forbidden:c,"not-found":a,"internal-server-error":i,"maintenance-error":l}[e]||a;return(0,p.jsxs)(p.Fragment,{children:[(0,p.jsxs)(f,{className:`border-b`,fixed:!0,children:[(0,p.jsx)(u,{}),(0,p.jsxs)(`div`,{className:`ms-auto flex items-center space-x-4`,children:[(0,p.jsx)(n,{}),(0,p.jsx)(r,{}),(0,p.jsx)(t,{}),(0,p.jsx)(d,{})]})]}),(0,p.jsx)(`div`,{className:`flex-1 [&>div]:h-full`,children:(0,p.jsx)(m,{})})]})}export{m as component};
|
||||
@@ -1,2 +0,0 @@
|
||||
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/_error-CvIS-wQi.js","assets/search-provider-B67mlhed.js","assets/chunk-DECur_0Z.js","assets/dist-CRowTfGU.js","assets/dist-D343v8gR.js","assets/messages-BLwDcEAr.js","assets/react-CO2uhaBc.js","assets/confirm-dialog-GGoKuiu9.js","assets/button-C0SPdqvs.js","assets/clsx-D9aGYY3V.js","assets/dist-BEGLjtzw.js","assets/dist-Cd7-TPaR.js","assets/dist-BphrRiSn.js","assets/dist-XiAcd8rT.js","assets/es2015-hwcIKy2q.js","assets/dist-C9d8iJDv.js","assets/dist-CYQVWHzW.js","assets/dist-CVKHDB42.js","assets/dist-DNb3IZ7I.js","assets/dist-THZWKIyP.js","assets/dist-BixeH3nX.js","assets/dist-DJTK1B2D.js","assets/separator-Di9zEmE_.js","assets/tooltip-8Hby-rrx.js","assets/dist-B1bXEkK2.js","assets/dist-Cm0GVWLR.js","assets/auth-store-paR9oIR_.js","assets/dist-CffCYhqm.js","assets/with-selector-DBfssCrY.js","assets/cookies-BzZOQYPw.js","assets/useRouter-DtTm7XkK.js","assets/useNavigate-7u4DX0Ho.js","assets/useStore-CupyIEEP.js","assets/command-vg2xchuv.js","assets/createLucideIcon-Br0Bd5k2.js","assets/dialog-iqE0q8bN.js","assets/skeleton-rs-vmKNc.js","assets/wallet-Bn5Vi2Ko.js","assets/sun-JU8p4FoE.js","assets/shield-check-MAPDL1u8.js","assets/logo-GNJA7dBE.js","assets/input-D-qaLZKc.js","assets/font-provider-BogPqUvU.js","assets/theme-provider-Cs4UKh_6.js","assets/theme-switch-B3KcLEky.js","assets/dropdown-menu-CDbt-i_J.js","assets/check-CHp0nSkR.js","assets/header-NBnXYgM_.js","assets/link-uW89koiO.js","assets/ClientOnly-CWlB586P.js","assets/forbidden-peSSCojP.js","assets/general-error-CDe5w_m4.js","assets/maintenance-error-C9XjssOV.js","assets/not-found-error-CTygh2Q3.js","assets/unauthorized-error-CASnS-6J.js"])))=>i.map(i=>d[i]);
|
||||
import{n as e,t}from"./lazyRouteComponent-BeVeaHRY.js";import{t as n}from"./preload-helper-DoS0eHac.js";var r=e(`/_authenticated/errors/$error`)({component:t(()=>n(()=>import(`./_error-CvIS-wQi.js`),__vite__mapDeps([0,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])),`component`)});export{r as t};
|
||||
@@ -0,0 +1,2 @@
|
||||
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/_trade_id-Busmu9hU.js","assets/chunk-DECur_0Z.js","assets/button-BgMnOYKD.js","assets/clsx-D9aGYY3V.js","assets/messages-Bp0bCjzp.js","assets/react-CO2uhaBc.js","assets/select-C9s05In_.js","assets/dist-BSXD7MjW.js","assets/dist-CKFLmh1A.js","assets/dist-B0ikDpJU.js","assets/dist-BFnxq45V.js","assets/dist-DGDEBCW9.js","assets/dist-CRowTfGU.js","assets/dist-C6i4A_Js.js","assets/dist-C97YBjnT.js","assets/dist-_ND6L-P3.js","assets/es2015-D8VLlhse.js","assets/dist-BixeH3nX.js","assets/dist-Bmn8KNt7.js","assets/createLucideIcon-Br0Bd5k2.js","assets/check-CHp0nSkR.js","assets/chevron-down-BB5h1CsX.js","assets/auth-store-Csn6HPxJ.js","assets/dist-BGqHIBUe.js","assets/with-selector-DBfssCrY.js","assets/cookies-BzZOQYPw.js","assets/useNavigate-7u4DX0Ho.js","assets/useRouter-DtTm7XkK.js","assets/copy-to-clipboard-C1tj4a8X.js","assets/arrow-left-right-CcCrkQiR.js","assets/arrow-left-BpZwGREZ.js","assets/radio-group-DrQ9k883.js","assets/dist-036CEgdV.js","assets/dist-Clua1vrx.js","assets/dist-D7AUoOWu.js","assets/loader-circle-DpEz1fQv.js","assets/send-BNvceEpO.js","assets/triangle-alert-DB5v_9sS.js","assets/dialog-BJ5VA2v_.js","assets/dist-CPQ-sRtL.js","assets/crypto-icon-DsKMU9xz.js","assets/labels-D0HnAPk9.js","assets/input-BleRUV2A.js","assets/checkout-model-CQ3aqlob.js"])))=>i.map(i=>d[i]);
|
||||
import{n as e,t}from"./lazyRouteComponent-iygd3-DM.js";import{t as n}from"./preload-helper-DoS0eHac.js";var r=e(`/cashier/$trade_id/`)({component:t(()=>n(()=>import(`./_trade_id-Busmu9hU.js`),__vite__mapDeps([0,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])),`component`)});export{r as t};
|
||||
File diff suppressed because one or more lines are too long
@@ -1,2 +0,0 @@
|
||||
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/_trade_id-Cd6oGTRE.js","assets/chunk-DECur_0Z.js","assets/button-C0SPdqvs.js","assets/clsx-D9aGYY3V.js","assets/messages-BLwDcEAr.js","assets/react-CO2uhaBc.js","assets/select-CNrSwHJR.js","assets/dist-B1bXEkK2.js","assets/dist-D343v8gR.js","assets/dist-BphrRiSn.js","assets/dist-XiAcd8rT.js","assets/dist-DJTK1B2D.js","assets/dist-CRowTfGU.js","assets/dist-THZWKIyP.js","assets/dist-CYQVWHzW.js","assets/dist-Cd7-TPaR.js","assets/es2015-hwcIKy2q.js","assets/dist-BixeH3nX.js","assets/dist-Cm0GVWLR.js","assets/createLucideIcon-Br0Bd5k2.js","assets/check-CHp0nSkR.js","assets/chevron-down-BB5h1CsX.js","assets/auth-store-paR9oIR_.js","assets/dist-CffCYhqm.js","assets/with-selector-DBfssCrY.js","assets/cookies-BzZOQYPw.js","assets/useNavigate-7u4DX0Ho.js","assets/useRouter-DtTm7XkK.js","assets/copy-to-clipboard-C1tj4a8X.js","assets/arrow-left-right-CcCrkQiR.js","assets/arrow-left-BpZwGREZ.js","assets/radio-group-D__IFVa8.js","assets/dist-CVKHDB42.js","assets/dist-C9d8iJDv.js","assets/dist-DNb3IZ7I.js","assets/loader-circle-DpEz1fQv.js","assets/send-BNvceEpO.js","assets/triangle-alert-DB5v_9sS.js","assets/dialog-iqE0q8bN.js","assets/dist-BEGLjtzw.js","assets/crypto-icon-CJd7sdrZ.js","assets/labels-BiExVADV.js","assets/input-D-qaLZKc.js","assets/checkout-model-CQ3aqlob.js"])))=>i.map(i=>d[i]);
|
||||
import{n as e,t}from"./lazyRouteComponent-BeVeaHRY.js";import{t as n}from"./preload-helper-DoS0eHac.js";var r=e(`/cashier/$trade_id/`)({component:t(()=>n(()=>import(`./_trade_id-Cd6oGTRE.js`),__vite__mapDeps([0,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])),`component`)});export{r as t};
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
import{Bp as e,Cn as t,Sn as n,Zp as r,_n as i,bn as a,vn as o,xn as s,yn as c,zp as l}from"./messages-BLwDcEAr.js";import{i as u,n as d,t as f}from"./button-C0SPdqvs.js";import{n as p,r as m}from"./font-provider-BogPqUvU.js";import{n as h}from"./theme-provider-Cs4UKh_6.js";import{t as g}from"./chevron-down-BB5h1CsX.js";import{n as _,t as v}from"./radio-group-D__IFVa8.js";import{r as y,s as b}from"./zod-rwFXxHlg.js";import{a as x,c as S,i as C,l as w,n as T,o as E,r as D,s as O,t as k}from"./form-fulEvgfz.js";import{t as A}from"./page-header-5Y8t-L2D.js";import{t as j}from"./show-submitted-data-COdV4uz4.js";var M=r(),N=b({theme:y([`light`,`dark`]),font:y(m)});function P(){let{font:t,setFont:n}=p(),{theme:r,setTheme:y}=h(),b={theme:r,font:t},A=w({resolver:S(N),defaultValues:b});function P(e){e.font!==t&&n(e.font),e.theme!==r&&y(e.theme),j(e)}return(0,M.jsx)(k,{...A,children:(0,M.jsxs)(`form`,{className:`space-y-8`,onSubmit:A.handleSubmit(P),children:[(0,M.jsx)(C,{control:A.control,name:`font`,render:({field:e})=>(0,M.jsxs)(x,{children:[(0,M.jsx)(E,{children:s()}),(0,M.jsxs)(`div`,{className:`relative w-max`,children:[(0,M.jsx)(T,{children:(0,M.jsx)(`select`,{className:u(d({variant:`outline`}),`w-50 appearance-none font-normal capitalize`,`dark:bg-background dark:hover:bg-background`),...e,children:m.map(e=>(0,M.jsx)(`option`,{value:e,children:e},e))})}),(0,M.jsx)(g,{className:`absolute inset-e-3 top-2.5 h-4 w-4 opacity-50`})]}),(0,M.jsx)(D,{className:`font-manrope`,children:a()}),(0,M.jsx)(O,{})]})}),(0,M.jsx)(C,{control:A.control,name:`theme`,render:({field:t})=>(0,M.jsxs)(x,{children:[(0,M.jsx)(E,{children:c()}),(0,M.jsx)(D,{children:o()}),(0,M.jsx)(O,{}),(0,M.jsxs)(v,{className:`grid max-w-md grid-cols-2 gap-8 pt-2`,defaultValue:t.value,onValueChange:t.onChange,children:[(0,M.jsx)(x,{children:(0,M.jsxs)(E,{className:`[&:has([data-state=checked])>div]:border-primary`,children:[(0,M.jsx)(T,{children:(0,M.jsx)(_,{className:`sr-only`,value:`light`})}),(0,M.jsx)(`div`,{className:`items-center rounded-md border-2 border-muted p-1 hover:border-accent`,children:(0,M.jsxs)(`div`,{className:`space-y-2 rounded-sm bg-[#ecedef] p-2`,children:[(0,M.jsxs)(`div`,{className:`space-y-2 rounded-md bg-white p-2 shadow-xs`,children:[(0,M.jsx)(`div`,{className:`h-2 w-20 rounded-lg bg-[#ecedef]`}),(0,M.jsx)(`div`,{className:`h-2 w-25 rounded-lg bg-[#ecedef]`})]}),(0,M.jsxs)(`div`,{className:`flex items-center space-x-2 rounded-md bg-white p-2 shadow-xs`,children:[(0,M.jsx)(`div`,{className:`h-4 w-4 rounded-full bg-[#ecedef]`}),(0,M.jsx)(`div`,{className:`h-2 w-25 rounded-lg bg-[#ecedef]`})]}),(0,M.jsxs)(`div`,{className:`flex items-center space-x-2 rounded-md bg-white p-2 shadow-xs`,children:[(0,M.jsx)(`div`,{className:`h-4 w-4 rounded-full bg-[#ecedef]`}),(0,M.jsx)(`div`,{className:`h-2 w-25 rounded-lg bg-[#ecedef]`})]})]})}),(0,M.jsx)(`span`,{className:`block w-full p-2 text-center font-normal`,children:e()})]})}),(0,M.jsx)(x,{children:(0,M.jsxs)(E,{className:`[&:has([data-state=checked])>div]:border-primary`,children:[(0,M.jsx)(T,{children:(0,M.jsx)(_,{className:`sr-only`,value:`dark`})}),(0,M.jsx)(`div`,{className:`items-center rounded-md border-2 border-muted bg-popover p-1 hover:bg-accent hover:text-accent-foreground`,children:(0,M.jsxs)(`div`,{className:`space-y-2 rounded-sm bg-slate-950 p-2`,children:[(0,M.jsxs)(`div`,{className:`space-y-2 rounded-md bg-slate-800 p-2 shadow-xs`,children:[(0,M.jsx)(`div`,{className:`h-2 w-20 rounded-lg bg-slate-400`}),(0,M.jsx)(`div`,{className:`h-2 w-25 rounded-lg bg-slate-400`})]}),(0,M.jsxs)(`div`,{className:`flex items-center space-x-2 rounded-md bg-slate-800 p-2 shadow-xs`,children:[(0,M.jsx)(`div`,{className:`h-4 w-4 rounded-full bg-slate-400`}),(0,M.jsx)(`div`,{className:`h-2 w-25 rounded-lg bg-slate-400`})]}),(0,M.jsxs)(`div`,{className:`flex items-center space-x-2 rounded-md bg-slate-800 p-2 shadow-xs`,children:[(0,M.jsx)(`div`,{className:`h-4 w-4 rounded-full bg-slate-400`}),(0,M.jsx)(`div`,{className:`h-2 w-25 rounded-lg bg-slate-400`})]})]})}),(0,M.jsx)(`span`,{className:`block w-full p-2 text-center font-normal`,children:l()})]})})]})]})}),(0,M.jsx)(f,{type:`submit`,children:i()})]})})}function F(){return(0,M.jsx)(A,{description:n(),title:t(),variant:`section`,children:(0,M.jsx)(P,{})})}var I=F;export{I as component};
|
||||
import{Cn as e,Hp as t,Sn as n,Up as r,_n as i,bn as a,em as o,vn as s,xn as c,yn as l}from"./messages-Bp0bCjzp.js";import{i as u,n as d,t as f}from"./button-BgMnOYKD.js";import{n as p,r as m}from"./font-provider-CtpKPVa7.js";import{n as h}from"./theme-provider-CyJJNAox.js";import{t as g}from"./chevron-down-BB5h1CsX.js";import{n as _,t as v}from"./radio-group-DrQ9k883.js";import{r as y,s as b}from"./zod-rwFXxHlg.js";import{a as x,c as S,i as C,l as w,n as T,o as E,r as D,s as O,t as k}from"./form-BhKmyN6D.js";import{t as A}from"./page-header-CDwG3nxs.js";import{t as j}from"./show-submitted-data-C8ocsjDF.js";var M=o(),N=b({theme:y([`light`,`dark`]),font:y(m)});function P(){let{font:e,setFont:n}=p(),{theme:o,setTheme:y}=h(),b={theme:o,font:e},A=w({resolver:S(N),defaultValues:b});function P(t){t.font!==e&&n(t.font),t.theme!==o&&y(t.theme),j(t)}return(0,M.jsx)(k,{...A,children:(0,M.jsxs)(`form`,{className:`space-y-8`,onSubmit:A.handleSubmit(P),children:[(0,M.jsx)(C,{control:A.control,name:`font`,render:({field:e})=>(0,M.jsxs)(x,{children:[(0,M.jsx)(E,{children:c()}),(0,M.jsxs)(`div`,{className:`relative w-max`,children:[(0,M.jsx)(T,{children:(0,M.jsx)(`select`,{className:u(d({variant:`outline`}),`w-50 appearance-none font-normal capitalize`,`dark:bg-background dark:hover:bg-background`),...e,children:m.map(e=>(0,M.jsx)(`option`,{value:e,children:e},e))})}),(0,M.jsx)(g,{className:`absolute inset-e-3 top-2.5 h-4 w-4 opacity-50`})]}),(0,M.jsx)(D,{className:`font-manrope`,children:a()}),(0,M.jsx)(O,{})]})}),(0,M.jsx)(C,{control:A.control,name:`theme`,render:({field:e})=>(0,M.jsxs)(x,{children:[(0,M.jsx)(E,{children:l()}),(0,M.jsx)(D,{children:s()}),(0,M.jsx)(O,{}),(0,M.jsxs)(v,{className:`grid max-w-md grid-cols-2 gap-8 pt-2`,defaultValue:e.value,onValueChange:e.onChange,children:[(0,M.jsx)(x,{children:(0,M.jsxs)(E,{className:`[&:has([data-state=checked])>div]:border-primary`,children:[(0,M.jsx)(T,{children:(0,M.jsx)(_,{className:`sr-only`,value:`light`})}),(0,M.jsx)(`div`,{className:`items-center rounded-md border-2 border-muted p-1 hover:border-accent`,children:(0,M.jsxs)(`div`,{className:`space-y-2 rounded-sm bg-[#ecedef] p-2`,children:[(0,M.jsxs)(`div`,{className:`space-y-2 rounded-md bg-white p-2 shadow-xs`,children:[(0,M.jsx)(`div`,{className:`h-2 w-20 rounded-lg bg-[#ecedef]`}),(0,M.jsx)(`div`,{className:`h-2 w-25 rounded-lg bg-[#ecedef]`})]}),(0,M.jsxs)(`div`,{className:`flex items-center space-x-2 rounded-md bg-white p-2 shadow-xs`,children:[(0,M.jsx)(`div`,{className:`h-4 w-4 rounded-full bg-[#ecedef]`}),(0,M.jsx)(`div`,{className:`h-2 w-25 rounded-lg bg-[#ecedef]`})]}),(0,M.jsxs)(`div`,{className:`flex items-center space-x-2 rounded-md bg-white p-2 shadow-xs`,children:[(0,M.jsx)(`div`,{className:`h-4 w-4 rounded-full bg-[#ecedef]`}),(0,M.jsx)(`div`,{className:`h-2 w-25 rounded-lg bg-[#ecedef]`})]})]})}),(0,M.jsx)(`span`,{className:`block w-full p-2 text-center font-normal`,children:r()})]})}),(0,M.jsx)(x,{children:(0,M.jsxs)(E,{className:`[&:has([data-state=checked])>div]:border-primary`,children:[(0,M.jsx)(T,{children:(0,M.jsx)(_,{className:`sr-only`,value:`dark`})}),(0,M.jsx)(`div`,{className:`items-center rounded-md border-2 border-muted bg-popover p-1 hover:bg-accent hover:text-accent-foreground`,children:(0,M.jsxs)(`div`,{className:`space-y-2 rounded-sm bg-slate-950 p-2`,children:[(0,M.jsxs)(`div`,{className:`space-y-2 rounded-md bg-slate-800 p-2 shadow-xs`,children:[(0,M.jsx)(`div`,{className:`h-2 w-20 rounded-lg bg-slate-400`}),(0,M.jsx)(`div`,{className:`h-2 w-25 rounded-lg bg-slate-400`})]}),(0,M.jsxs)(`div`,{className:`flex items-center space-x-2 rounded-md bg-slate-800 p-2 shadow-xs`,children:[(0,M.jsx)(`div`,{className:`h-4 w-4 rounded-full bg-slate-400`}),(0,M.jsx)(`div`,{className:`h-2 w-25 rounded-lg bg-slate-400`})]}),(0,M.jsxs)(`div`,{className:`flex items-center space-x-2 rounded-md bg-slate-800 p-2 shadow-xs`,children:[(0,M.jsx)(`div`,{className:`h-4 w-4 rounded-full bg-slate-400`}),(0,M.jsx)(`div`,{className:`h-2 w-25 rounded-lg bg-slate-400`})]})]})}),(0,M.jsx)(`span`,{className:`block w-full p-2 text-center font-normal`,children:t()})]})})]})]})}),(0,M.jsx)(f,{type:`submit`,children:i()})]})})}function F(){return(0,M.jsx)(A,{description:n(),title:e(),variant:`section`,children:(0,M.jsx)(P,{})})}var I=F;export{I as component};
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{r as e}from"./chunk-DECur_0Z.js";import{t}from"./react-CO2uhaBc.js";import{Zp as n}from"./messages-BLwDcEAr.js";var r=e(t(),1),i=n(),a=(0,r.createContext)({isTyping:!1,passwordLength:0,setIsTyping:()=>void 0,setPasswordLength:()=>void 0,setShowPassword:()=>void 0,showPassword:!1});function o({children:e}){let[t,n]=(0,r.useState)(!1),[o,s]=(0,r.useState)(!1),[c,l]=(0,r.useState)(0);return(0,i.jsx)(a.Provider,{value:{isTyping:t,passwordLength:c,setIsTyping:n,setPasswordLength:l,setShowPassword:s,showPassword:o},children:e})}function s(){return(0,r.useContext)(a)}export{s as n,o as t};
|
||||
import{r as e}from"./chunk-DECur_0Z.js";import{t}from"./react-CO2uhaBc.js";import{em as n}from"./messages-Bp0bCjzp.js";var r=e(t(),1),i=n(),a=(0,r.createContext)({isTyping:!1,passwordLength:0,setIsTyping:()=>void 0,setPasswordLength:()=>void 0,setShowPassword:()=>void 0,showPassword:!1});function o({children:e}){let[t,n]=(0,r.useState)(!1),[o,s]=(0,r.useState)(!1),[c,l]=(0,r.useState)(0);return(0,i.jsx)(a.Provider,{value:{isTyping:t,passwordLength:c,setIsTyping:n,setPasswordLength:l,setShowPassword:s,showPassword:o},children:e})}function s(){return(0,r.useContext)(a)}export{s as n,o as t};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
import{Zp as e}from"./messages-BLwDcEAr.js";import{i as t,o as n,r}from"./button-C0SPdqvs.js";var i=e(),a=r(`inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden whitespace-nowrap rounded-full border border-transparent px-2 py-0.5 font-medium text-xs transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3`,{variants:{variant:{default:`bg-primary text-primary-foreground [a&]:hover:bg-primary/90`,secondary:`bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90`,destructive:`bg-destructive text-white focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40 [a&]:hover:bg-destructive/90`,outline:`border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground`,ghost:`[a&]:hover:bg-accent [a&]:hover:text-accent-foreground`,link:`text-primary underline-offset-4 [a&]:hover:underline`}},defaultVariants:{variant:`default`}});function o({className:e,variant:r=`default`,asChild:o=!1,...s}){return(0,i.jsx)(o?n:`span`,{className:t(a({variant:r}),e),"data-slot":`badge`,"data-variant":r,...s})}export{o as t};
|
||||
import{em as e}from"./messages-Bp0bCjzp.js";import{i as t,o as n,r}from"./button-BgMnOYKD.js";var i=e(),a=r(`inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden whitespace-nowrap rounded-full border border-transparent px-2 py-0.5 font-medium text-xs transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3`,{variants:{variant:{default:`bg-primary text-primary-foreground [a&]:hover:bg-primary/90`,secondary:`bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90`,destructive:`bg-destructive text-white focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40 [a&]:hover:bg-destructive/90`,outline:`border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground`,ghost:`[a&]:hover:bg-accent [a&]:hover:text-accent-foreground`,link:`text-primary underline-offset-4 [a&]:hover:underline`}},defaultVariants:{variant:`default`}});function o({className:e,variant:r=`default`,asChild:o=!1,...s}){return(0,i.jsx)(o?n:`span`,{className:t(a({variant:r}),e),"data-slot":`badge`,"data-variant":r,...s})}export{o as t};
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
import{Zp as e}from"./messages-BLwDcEAr.js";import{i as t}from"./button-C0SPdqvs.js";var n=e();function r({className:e,...r}){return(0,n.jsx)(`div`,{className:t(`flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-sm`,e),"data-slot":`card`,...r})}function i({className:e,...r}){return(0,n.jsx)(`div`,{className:t(`@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6`,e),"data-slot":`card-header`,...r})}function a({className:e,...r}){return(0,n.jsx)(`div`,{className:t(`font-semibold leading-none`,e),"data-slot":`card-title`,...r})}function o({className:e,...r}){return(0,n.jsx)(`div`,{className:t(`text-muted-foreground text-sm`,e),"data-slot":`card-description`,...r})}function s({className:e,...r}){return(0,n.jsx)(`div`,{className:t(`px-6`,e),"data-slot":`card-content`,...r})}export{a,i,s as n,o as r,r as t};
|
||||
import{em as e}from"./messages-Bp0bCjzp.js";import{i as t}from"./button-BgMnOYKD.js";var n=e();function r({className:e,...r}){return(0,n.jsx)(`div`,{className:t(`flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-sm`,e),"data-slot":`card`,...r})}function i({className:e,...r}){return(0,n.jsx)(`div`,{className:t(`@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6`,e),"data-slot":`card-header`,...r})}function a({className:e,...r}){return(0,n.jsx)(`div`,{className:t(`font-semibold leading-none`,e),"data-slot":`card-title`,...r})}function o({className:e,...r}){return(0,n.jsx)(`div`,{className:t(`text-muted-foreground text-sm`,e),"data-slot":`card-description`,...r})}function s({className:e,...r}){return(0,n.jsx)(`div`,{className:t(`px-6`,e),"data-slot":`card-content`,...r})}export{a,i,s as n,o as r,r as t};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
import{r as e}from"./chunk-DECur_0Z.js";import{a as t,d as n,f as r}from"./auth-store-paR9oIR_.js";import{t as i}from"./react-CO2uhaBc.js";import{t as a}from"./createLucideIcon-Br0Bd5k2.js";var o=a(`pen-line`,[[`path`,{d:`M13 21h8`,key:`1jsn5i`}],[`path`,{d:`M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z`,key:`1a8usu`}]]),s=e(i(),1),c=new n({chains:[],loading:!1});function l(e){c.setState(t=>({...t,chains:e}))}function u(e){c.setState(t=>({...t,loading:e}))}function d(){let e=r(c,e=>e.chains),n=r(c,e=>e.loading),i=t.useQuery(`get`,`/admin/api/v1/chains`);return(0,s.useEffect)(()=>{u(i.isLoading)},[i.isLoading]),(0,s.useEffect)(()=>{i.data?.data&&l(i.data.data)},[i.data?.data]),{chains:e,loading:n,refetch:i.refetch}}export{o as n,d as t};
|
||||
import{r as e}from"./chunk-DECur_0Z.js";import{a as t,d as n,f as r}from"./auth-store-Csn6HPxJ.js";import{t as i}from"./react-CO2uhaBc.js";import{t as a}from"./createLucideIcon-Br0Bd5k2.js";var o=a(`pen-line`,[[`path`,{d:`M13 21h8`,key:`1jsn5i`}],[`path`,{d:`M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z`,key:`1a8usu`}]]),s=e(i(),1),c=new n({chains:[],loading:!1});function l(e){c.setState(t=>({...t,chains:e}))}function u(e){c.setState(t=>({...t,loading:e}))}function d(){let e=r(c,e=>e.chains),n=r(c,e=>e.loading),i=t.useQuery(`get`,`/admin/api/v1/chains`);return(0,s.useEffect)(()=>{u(i.isLoading)},[i.isLoading]),(0,s.useEffect)(()=>{i.data?.data&&l(i.data.data)},[i.data?.data]),{chains:e,loading:n,refetch:i.refetch}}export{o as n,d as t};
|
||||
@@ -1 +1 @@
|
||||
import{r as e}from"./chunk-DECur_0Z.js";import{t}from"./react-CO2uhaBc.js";import{Zp as n}from"./messages-BLwDcEAr.js";import{t as r}from"./dist-XiAcd8rT.js";import{i,u as a}from"./button-C0SPdqvs.js";import{a as o,r as s,t as c}from"./dist-D343v8gR.js";import{t as l}from"./dist-C9d8iJDv.js";import{t as u}from"./dist-BixeH3nX.js";import{t as d}from"./dist-DJTK1B2D.js";import{t as f}from"./check-CHp0nSkR.js";var p=e(t(),1),m=n(),h=`Checkbox`,[g,_]=o(h),[v,y]=g(h);function b(e){let{__scopeCheckbox:t,checked:n,children:r,defaultChecked:i,disabled:a,form:o,name:s,onCheckedChange:l,required:u,value:d=`on`,internal_do_not_use_render:f}=e,[g,_]=c({prop:n,defaultProp:i??!1,onChange:l,caller:h}),[y,b]=p.useState(null),[x,S]=p.useState(null),C=p.useRef(!1),w=y?!!o||!!y.closest(`form`):!0,T={checked:g,disabled:a,setChecked:_,control:y,setControl:b,name:s,form:o,value:d,hasConsumerStoppedPropagationRef:C,required:u,defaultChecked:k(i)?!1:i,isFormControl:w,bubbleInput:x,setBubbleInput:S};return(0,m.jsx)(v,{scope:t,...T,children:O(f)?f(T):r})}var x=`CheckboxTrigger`,S=p.forwardRef(({__scopeCheckbox:e,onKeyDown:t,onClick:n,...i},o)=>{let{control:c,value:l,disabled:u,checked:d,required:f,setControl:h,setChecked:g,hasConsumerStoppedPropagationRef:_,isFormControl:v,bubbleInput:b}=y(x,e),S=a(o,h),C=p.useRef(d);return p.useEffect(()=>{let e=c?.form;if(e){let t=()=>g(C.current);return e.addEventListener(`reset`,t),()=>e.removeEventListener(`reset`,t)}},[c,g]),(0,m.jsx)(r.button,{type:`button`,role:`checkbox`,"aria-checked":k(d)?`mixed`:d,"aria-required":f,"data-state":A(d),"data-disabled":u?``:void 0,disabled:u,value:l,...i,ref:S,onKeyDown:s(t,e=>{e.key===`Enter`&&e.preventDefault()}),onClick:s(n,e=>{g(e=>k(e)?!0:!e),b&&v&&(_.current=e.isPropagationStopped(),_.current||e.stopPropagation())})})});S.displayName=x;var C=p.forwardRef((e,t)=>{let{__scopeCheckbox:n,name:r,checked:i,defaultChecked:a,required:o,disabled:s,value:c,onCheckedChange:l,form:u,...d}=e;return(0,m.jsx)(b,{__scopeCheckbox:n,checked:i,defaultChecked:a,disabled:s,required:o,onCheckedChange:l,name:r,form:u,value:c,internal_do_not_use_render:({isFormControl:e})=>(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(S,{...d,ref:t,__scopeCheckbox:n}),e&&(0,m.jsx)(D,{__scopeCheckbox:n})]})})});C.displayName=h;var w=`CheckboxIndicator`,T=p.forwardRef((e,t)=>{let{__scopeCheckbox:n,forceMount:i,...a}=e,o=y(w,n);return(0,m.jsx)(l,{present:i||k(o.checked)||o.checked===!0,children:(0,m.jsx)(r.span,{"data-state":A(o.checked),"data-disabled":o.disabled?``:void 0,...a,ref:t,style:{pointerEvents:`none`,...e.style}})})});T.displayName=w;var E=`CheckboxBubbleInput`,D=p.forwardRef(({__scopeCheckbox:e,...t},n)=>{let{control:i,hasConsumerStoppedPropagationRef:o,checked:s,defaultChecked:c,required:l,disabled:f,name:h,value:g,form:_,bubbleInput:v,setBubbleInput:b}=y(E,e),x=a(n,b),S=u(s),C=d(i);p.useEffect(()=>{let e=v;if(!e)return;let t=window.HTMLInputElement.prototype,n=Object.getOwnPropertyDescriptor(t,`checked`).set,r=!o.current;if(S!==s&&n){let t=new Event(`click`,{bubbles:r});e.indeterminate=k(s),n.call(e,k(s)?!1:s),e.dispatchEvent(t)}},[v,S,s,o]);let w=p.useRef(k(s)?!1:s);return(0,m.jsx)(r.input,{type:`checkbox`,"aria-hidden":!0,defaultChecked:c??w.current,required:l,disabled:f,name:h,value:g,form:_,...t,tabIndex:-1,ref:x,style:{...t.style,...C,position:`absolute`,pointerEvents:`none`,opacity:0,margin:0,transform:`translateX(-100%)`}})});D.displayName=E;function O(e){return typeof e==`function`}function k(e){return e===`indeterminate`}function A(e){return k(e)?`indeterminate`:e?`checked`:`unchecked`}function j({className:e,...t}){return(0,m.jsx)(C,{className:i(`peer size-4 shrink-0 rounded-[4px] border border-input shadow-xs outline-none transition-shadow focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:bg-input/30 dark:data-[state=checked]:bg-primary dark:aria-invalid:ring-destructive/40`,e),"data-slot":`checkbox`,...t,children:(0,m.jsx)(T,{className:`grid place-content-center text-current transition-none`,"data-slot":`checkbox-indicator`,children:(0,m.jsx)(f,{className:`size-3.5`})})})}export{j as t};
|
||||
import{r as e}from"./chunk-DECur_0Z.js";import{t}from"./react-CO2uhaBc.js";import{em as n}from"./messages-Bp0bCjzp.js";import{t as r}from"./dist-BFnxq45V.js";import{i,u as a}from"./button-BgMnOYKD.js";import{a as o,r as s,t as c}from"./dist-CKFLmh1A.js";import{t as l}from"./dist-Clua1vrx.js";import{t as u}from"./dist-BixeH3nX.js";import{t as d}from"./dist-DGDEBCW9.js";import{t as f}from"./check-CHp0nSkR.js";var p=e(t(),1),m=n(),h=`Checkbox`,[g,_]=o(h),[v,y]=g(h);function b(e){let{__scopeCheckbox:t,checked:n,children:r,defaultChecked:i,disabled:a,form:o,name:s,onCheckedChange:l,required:u,value:d=`on`,internal_do_not_use_render:f}=e,[g,_]=c({prop:n,defaultProp:i??!1,onChange:l,caller:h}),[y,b]=p.useState(null),[x,S]=p.useState(null),C=p.useRef(!1),w=y?!!o||!!y.closest(`form`):!0,T={checked:g,disabled:a,setChecked:_,control:y,setControl:b,name:s,form:o,value:d,hasConsumerStoppedPropagationRef:C,required:u,defaultChecked:k(i)?!1:i,isFormControl:w,bubbleInput:x,setBubbleInput:S};return(0,m.jsx)(v,{scope:t,...T,children:O(f)?f(T):r})}var x=`CheckboxTrigger`,S=p.forwardRef(({__scopeCheckbox:e,onKeyDown:t,onClick:n,...i},o)=>{let{control:c,value:l,disabled:u,checked:d,required:f,setControl:h,setChecked:g,hasConsumerStoppedPropagationRef:_,isFormControl:v,bubbleInput:b}=y(x,e),S=a(o,h),C=p.useRef(d);return p.useEffect(()=>{let e=c?.form;if(e){let t=()=>g(C.current);return e.addEventListener(`reset`,t),()=>e.removeEventListener(`reset`,t)}},[c,g]),(0,m.jsx)(r.button,{type:`button`,role:`checkbox`,"aria-checked":k(d)?`mixed`:d,"aria-required":f,"data-state":A(d),"data-disabled":u?``:void 0,disabled:u,value:l,...i,ref:S,onKeyDown:s(t,e=>{e.key===`Enter`&&e.preventDefault()}),onClick:s(n,e=>{g(e=>k(e)?!0:!e),b&&v&&(_.current=e.isPropagationStopped(),_.current||e.stopPropagation())})})});S.displayName=x;var C=p.forwardRef((e,t)=>{let{__scopeCheckbox:n,name:r,checked:i,defaultChecked:a,required:o,disabled:s,value:c,onCheckedChange:l,form:u,...d}=e;return(0,m.jsx)(b,{__scopeCheckbox:n,checked:i,defaultChecked:a,disabled:s,required:o,onCheckedChange:l,name:r,form:u,value:c,internal_do_not_use_render:({isFormControl:e})=>(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(S,{...d,ref:t,__scopeCheckbox:n}),e&&(0,m.jsx)(D,{__scopeCheckbox:n})]})})});C.displayName=h;var w=`CheckboxIndicator`,T=p.forwardRef((e,t)=>{let{__scopeCheckbox:n,forceMount:i,...a}=e,o=y(w,n);return(0,m.jsx)(l,{present:i||k(o.checked)||o.checked===!0,children:(0,m.jsx)(r.span,{"data-state":A(o.checked),"data-disabled":o.disabled?``:void 0,...a,ref:t,style:{pointerEvents:`none`,...e.style}})})});T.displayName=w;var E=`CheckboxBubbleInput`,D=p.forwardRef(({__scopeCheckbox:e,...t},n)=>{let{control:i,hasConsumerStoppedPropagationRef:o,checked:s,defaultChecked:c,required:l,disabled:f,name:h,value:g,form:_,bubbleInput:v,setBubbleInput:b}=y(E,e),x=a(n,b),S=u(s),C=d(i);p.useEffect(()=>{let e=v;if(!e)return;let t=window.HTMLInputElement.prototype,n=Object.getOwnPropertyDescriptor(t,`checked`).set,r=!o.current;if(S!==s&&n){let t=new Event(`click`,{bubbles:r});e.indeterminate=k(s),n.call(e,k(s)?!1:s),e.dispatchEvent(t)}},[v,S,s,o]);let w=p.useRef(k(s)?!1:s);return(0,m.jsx)(r.input,{type:`checkbox`,"aria-hidden":!0,defaultChecked:c??w.current,required:l,disabled:f,name:h,value:g,form:_,...t,tabIndex:-1,ref:x,style:{...t.style,...C,position:`absolute`,pointerEvents:`none`,opacity:0,margin:0,transform:`translateX(-100%)`}})});D.displayName=E;function O(e){return typeof e==`function`}function k(e){return e===`indeterminate`}function A(e){return k(e)?`indeterminate`:e?`checked`:`unchecked`}function j({className:e,...t}){return(0,m.jsx)(C,{className:i(`peer size-4 shrink-0 rounded-[4px] border border-input shadow-xs outline-none transition-shadow focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:bg-input/30 dark:data-[state=checked]:bg-primary dark:aria-invalid:ring-destructive/40`,e),"data-slot":`checkbox`,...t,children:(0,m.jsx)(T,{className:`grid place-content-center text-current transition-none`,"data-slot":`checkbox-indicator`,children:(0,m.jsx)(f,{className:`size-3.5`})})})}export{j as t};
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{Mp as e,Np as t,Pp as n,Zp as r}from"./messages-BLwDcEAr.js";import{t as i}from"./telescope-DU0wiTDw.js";import{i as a,o,r as s,s as c,t as l}from"./dialog-iqE0q8bN.js";var u=r();function d({open:r,onOpenChange:d}){return(0,u.jsx)(l,{onOpenChange:d,open:r,children:(0,u.jsx)(s,{className:`sm:max-w-sm`,children:(0,u.jsxs)(o,{className:`items-center text-center`,children:[(0,u.jsx)(i,{className:`mb-2 size-12 text-muted-foreground`}),(0,u.jsx)(c,{children:n()}),(0,u.jsxs)(a,{children:[t(),(0,u.jsx)(`br`,{}),(0,u.jsx)(`p`,{className:`mt-2 text-center`,children:e()})]})]})})})}export{d as t};
|
||||
import{Fp as e,Ip as t,Lp as n,em as r}from"./messages-Bp0bCjzp.js";import{t as i}from"./telescope-DU0wiTDw.js";import{i as a,o,r as s,s as c,t as l}from"./dialog-BJ5VA2v_.js";var u=r();function d({open:r,onOpenChange:d}){return(0,u.jsx)(l,{onOpenChange:d,open:r,children:(0,u.jsx)(s,{className:`sm:max-w-sm`,children:(0,u.jsxs)(o,{className:`items-center text-center`,children:[(0,u.jsx)(i,{className:`mb-2 size-12 text-muted-foreground`}),(0,u.jsx)(c,{children:n()}),(0,u.jsxs)(a,{children:[t(),(0,u.jsx)(`br`,{}),(0,u.jsx)(`p`,{className:`mt-2 text-center`,children:e()})]})]})})})}export{d as t};
|
||||
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{r as e}from"./chunk-DECur_0Z.js";import{t}from"./react-CO2uhaBc.js";import{Gp as n,Kp as r,Zp as i}from"./messages-BLwDcEAr.js";import{c as a,i as o,t as s,u as c}from"./button-C0SPdqvs.js";import{a as l,r as u}from"./dist-D343v8gR.js";import{a as d,c as f,i as p,l as m,n as h,o as g,r as _,s as ee,t as v,u as y}from"./dist-BEGLjtzw.js";var b=e(t(),1),x=i(),S=`AlertDialog`,[C,te]=l(S,[y]),w=y(),T=e=>{let{__scopeAlertDialog:t,...n}=e,r=w(t);return(0,x.jsx)(g,{...r,...n,modal:!0})};T.displayName=S;var E=`AlertDialogTrigger`,D=b.forwardRef((e,t)=>{let{__scopeAlertDialog:n,...r}=e,i=w(n);return(0,x.jsx)(f,{...i,...r,ref:t})});D.displayName=E;var O=`AlertDialogPortal`,k=e=>{let{__scopeAlertDialog:t,...n}=e,r=w(t);return(0,x.jsx)(d,{...r,...n})};k.displayName=O;var A=`AlertDialogOverlay`,j=b.forwardRef((e,t)=>{let{__scopeAlertDialog:n,...r}=e,i=w(n);return(0,x.jsx)(p,{...i,...r,ref:t})});j.displayName=A;var M=`AlertDialogContent`,[N,P]=C(M),F=a(`AlertDialogContent`),I=b.forwardRef((e,t)=>{let{__scopeAlertDialog:n,children:r,...i}=e,a=w(n),o=b.useRef(null),s=c(t,o),l=b.useRef(null);return(0,x.jsx)(m,{contentName:M,titleName:L,docsSlug:`alert-dialog`,children:(0,x.jsx)(N,{scope:n,cancelRef:l,children:(0,x.jsxs)(h,{role:`alertdialog`,...a,...i,ref:s,onOpenAutoFocus:u(i.onOpenAutoFocus,e=>{e.preventDefault(),l.current?.focus({preventScroll:!0})}),onPointerDownOutside:e=>e.preventDefault(),onInteractOutside:e=>e.preventDefault(),children:[(0,x.jsx)(F,{children:r}),(0,x.jsx)(G,{contentRef:o})]})})})});I.displayName=M;var L=`AlertDialogTitle`,R=b.forwardRef((e,t)=>{let{__scopeAlertDialog:n,...r}=e,i=w(n);return(0,x.jsx)(ee,{...i,...r,ref:t})});R.displayName=L;var z=`AlertDialogDescription`,B=b.forwardRef((e,t)=>{let{__scopeAlertDialog:n,...r}=e,i=w(n);return(0,x.jsx)(_,{...i,...r,ref:t})});B.displayName=z;var V=`AlertDialogAction`,H=b.forwardRef((e,t)=>{let{__scopeAlertDialog:n,...r}=e,i=w(n);return(0,x.jsx)(v,{...i,...r,ref:t})});H.displayName=V;var U=`AlertDialogCancel`,W=b.forwardRef((e,t)=>{let{__scopeAlertDialog:n,...r}=e,{cancelRef:i}=P(U,n),a=w(n),o=c(t,i);return(0,x.jsx)(v,{...a,...r,ref:o})});W.displayName=U;var G=({contentRef:e})=>{let t=`\`${M}\` requires a description for the component to be accessible for screen reader users.
|
||||
import{r as e}from"./chunk-DECur_0Z.js";import{t}from"./react-CO2uhaBc.js";import{Jp as n,Yp as r,em as i}from"./messages-Bp0bCjzp.js";import{c as a,i as o,t as s,u as c}from"./button-BgMnOYKD.js";import{a as l,r as u}from"./dist-CKFLmh1A.js";import{a as d,c as f,i as p,l as m,n as h,o as g,r as _,s as ee,t as v,u as y}from"./dist-CPQ-sRtL.js";var b=e(t(),1),x=i(),S=`AlertDialog`,[C,te]=l(S,[y]),w=y(),T=e=>{let{__scopeAlertDialog:t,...n}=e,r=w(t);return(0,x.jsx)(g,{...r,...n,modal:!0})};T.displayName=S;var E=`AlertDialogTrigger`,D=b.forwardRef((e,t)=>{let{__scopeAlertDialog:n,...r}=e,i=w(n);return(0,x.jsx)(f,{...i,...r,ref:t})});D.displayName=E;var O=`AlertDialogPortal`,k=e=>{let{__scopeAlertDialog:t,...n}=e,r=w(t);return(0,x.jsx)(d,{...r,...n})};k.displayName=O;var A=`AlertDialogOverlay`,j=b.forwardRef((e,t)=>{let{__scopeAlertDialog:n,...r}=e,i=w(n);return(0,x.jsx)(p,{...i,...r,ref:t})});j.displayName=A;var M=`AlertDialogContent`,[N,P]=C(M),F=a(`AlertDialogContent`),I=b.forwardRef((e,t)=>{let{__scopeAlertDialog:n,children:r,...i}=e,a=w(n),o=b.useRef(null),s=c(t,o),l=b.useRef(null);return(0,x.jsx)(m,{contentName:M,titleName:L,docsSlug:`alert-dialog`,children:(0,x.jsx)(N,{scope:n,cancelRef:l,children:(0,x.jsxs)(h,{role:`alertdialog`,...a,...i,ref:s,onOpenAutoFocus:u(i.onOpenAutoFocus,e=>{e.preventDefault(),l.current?.focus({preventScroll:!0})}),onPointerDownOutside:e=>e.preventDefault(),onInteractOutside:e=>e.preventDefault(),children:[(0,x.jsx)(F,{children:r}),(0,x.jsx)(G,{contentRef:o})]})})})});I.displayName=M;var L=`AlertDialogTitle`,R=b.forwardRef((e,t)=>{let{__scopeAlertDialog:n,...r}=e,i=w(n);return(0,x.jsx)(ee,{...i,...r,ref:t})});R.displayName=L;var z=`AlertDialogDescription`,B=b.forwardRef((e,t)=>{let{__scopeAlertDialog:n,...r}=e,i=w(n);return(0,x.jsx)(_,{...i,...r,ref:t})});B.displayName=z;var V=`AlertDialogAction`,H=b.forwardRef((e,t)=>{let{__scopeAlertDialog:n,...r}=e,i=w(n);return(0,x.jsx)(v,{...i,...r,ref:t})});H.displayName=V;var U=`AlertDialogCancel`,W=b.forwardRef((e,t)=>{let{__scopeAlertDialog:n,...r}=e,{cancelRef:i}=P(U,n),a=w(n),o=c(t,i);return(0,x.jsx)(v,{...a,...r,ref:o})});W.displayName=U;var G=({contentRef:e})=>{let t=`\`${M}\` requires a description for the component to be accessible for screen reader users.
|
||||
|
||||
You can add a description to the \`${M}\` by passing a \`${z}\` component as a child, which also benefits sighted users by adding visible context to the dialog.
|
||||
|
||||
@@ -1 +1 @@
|
||||
import{r as e}from"./chunk-DECur_0Z.js";import{t}from"./react-CO2uhaBc.js";import{Zp as n}from"./messages-BLwDcEAr.js";var r=e(t(),1);function i(e){let t=new Map,n=new Map;return{get(e){return t.get(e)},load(r){let i=t.get(r);if(i)return Promise.resolve(i);let a=n.get(r);if(a)return a;let o=e(r).then(e=>(t.set(r,e),e)).finally(()=>{n.delete(r)});return n.set(r,o),o}}}async function a(e,t,n){for(let n of e){let e=`${n.base}/${t}`;try{let t=await fetch(e,{signal:AbortSignal.timeout(3e3)});if(t.ok)return await t.json()}catch{}}return n}function o({cdnList:e,fallback:t,path:n}){return i(r=>a(e,n(r),t))}var s=`GMWalletApp/crypto-icons`,c=`latest`,l=[{base:`https://cdn.jsdmirror.com/gh/${s}@${c}`,name:`jsdmirror`},{base:`https://cdn.jsdelivr.net/gh/${s}@${c}`,name:`jsdelivr`},{base:`https://fastly.jsdelivr.net/gh/${s}@${c}`,name:`fastly.jsdelivr`},{base:`https://gcore.jsdelivr.net/gh/${s}@${c}`,name:`gcore.jsdelivr`}],u={exchange:`exchanges`,network:`networks`,token:`tokens`,wallet:`wallets`},d=o({cdnList:l,fallback:{},path:e=>`aliases/${u[e]}.json`}),f=o({cdnList:l,fallback:[],path:e=>`maps/${u[e]}.json`});function p(e){return String(e??``).trim().toLowerCase()}function m(e){return p(e).replace(/\s+/g,`-`)}function h(e){let t=e?.split(`:`).pop();return t?t.trim():``}function g(e){return[e.id,e.symbol,e.name,e.shortName,h(e.filePath)].map(e=>m(e??``)).filter(Boolean)}function _(e,t){let n=e.find(e=>g(e).includes(t));return n&&(h(n.filePath)||n.id||n.symbol)||null}async function v(e,t){let n=m(t);if(!n)return``;let[r,i]=await Promise.all([f.load(e),d.load(e)]),a=m(i[n]??``);return _(r,n)||(a?_(r,a):null)||i[n]||n}function y(e,t,n={}){let{variant:r=`branded`}=n,i=m(t);return i?`${l[0].base}/assets/${u[e]}/${r}/${i}.svg`:null}async function b(e,t,n={}){let{variant:r=`branded`}=n,i=await v(e,t),a=`assets/${u[e]}/${r}/${i}.svg`;for(let e of l){let t=`${e.base}/${a}`;try{if((await fetch(t,{method:`HEAD`,signal:AbortSignal.timeout(2500)})).ok)return{url:t}}catch{}}return{url:`${l[0].base}/${a}`}}function x(e,t){return t?.trim()||e||`--`}var S=n();function C({type:e,name:t,variant:n=`branded`,size:i=16,width:a,height:o,className:s,alt:c,style:l,onError:u,...d}){let f=y(e,t,{variant:n}),[p,m]=(0,r.useState)(null),[h,g]=(0,r.useState)(f),_=h??f,v=a??i,x=o??i;return(0,r.useEffect)(()=>{let r=!1;return m(null),g(y(e,t,{variant:n})),b(e,t,{variant:n}).then(e=>{r||g(e.url)}),()=>{r=!0}},[t,e,n]),!(_&&t)||p===_?null:(0,S.jsx)(`img`,{alt:c??``,className:s,height:x,onError:e=>{m(_),u?.(e)},src:_,style:{display:`inline-block`,...l},width:v,...d})}export{p as a,l as i,x as n,v as o,u as r,o as s,C as t};
|
||||
import{r as e}from"./chunk-DECur_0Z.js";import{t}from"./react-CO2uhaBc.js";import{em as n}from"./messages-Bp0bCjzp.js";var r=e(t(),1);function i(e){let t=new Map,n=new Map;return{get(e){return t.get(e)},load(r){let i=t.get(r);if(i)return Promise.resolve(i);let a=n.get(r);if(a)return a;let o=e(r).then(e=>(t.set(r,e),e)).finally(()=>{n.delete(r)});return n.set(r,o),o}}}async function a(e,t,n){for(let n of e){let e=`${n.base}/${t}`;try{let t=await fetch(e,{signal:AbortSignal.timeout(3e3)});if(t.ok)return await t.json()}catch{}}return n}function o({cdnList:e,fallback:t,path:n}){return i(r=>a(e,n(r),t))}var s=`GMWalletApp/crypto-icons`,c=`latest`,l=[{base:`https://cdn.jsdmirror.com/gh/${s}@${c}`,name:`jsdmirror`},{base:`https://cdn.jsdelivr.net/gh/${s}@${c}`,name:`jsdelivr`},{base:`https://fastly.jsdelivr.net/gh/${s}@${c}`,name:`fastly.jsdelivr`},{base:`https://gcore.jsdelivr.net/gh/${s}@${c}`,name:`gcore.jsdelivr`}],u={exchange:`exchanges`,network:`networks`,token:`tokens`,wallet:`wallets`},d=o({cdnList:l,fallback:{},path:e=>`aliases/${u[e]}.json`}),f=o({cdnList:l,fallback:[],path:e=>`maps/${u[e]}.json`});function p(e){return String(e??``).trim().toLowerCase()}function m(e){return p(e).replace(/\s+/g,`-`)}function h(e){let t=e?.split(`:`).pop();return t?t.trim():``}function g(e){return[e.id,e.symbol,e.name,e.shortName,h(e.filePath)].map(e=>m(e??``)).filter(Boolean)}function _(e,t){let n=e.find(e=>g(e).includes(t));return n&&(h(n.filePath)||n.id||n.symbol)||null}async function v(e,t){let n=m(t);if(!n)return``;let[r,i]=await Promise.all([f.load(e),d.load(e)]),a=m(i[n]??``);return _(r,n)||(a?_(r,a):null)||i[n]||n}function y(e,t,n={}){let{variant:r=`branded`}=n,i=m(t);return i?`${l[0].base}/assets/${u[e]}/${r}/${i}.svg`:null}async function b(e,t,n={}){let{variant:r=`branded`}=n,i=await v(e,t),a=`assets/${u[e]}/${r}/${i}.svg`;for(let e of l){let t=`${e.base}/${a}`;try{if((await fetch(t,{method:`HEAD`,signal:AbortSignal.timeout(2500)})).ok)return{url:t}}catch{}}return{url:`${l[0].base}/${a}`}}function x(e,t){return t?.trim()||e||`--`}var S=n();function C({type:e,name:t,variant:n=`branded`,size:i=16,width:a,height:o,className:s,alt:c,style:l,onError:u,...d}){let f=y(e,t,{variant:n}),[p,m]=(0,r.useState)(null),[h,g]=(0,r.useState)(f),_=h??f,v=a??i,x=o??i;return(0,r.useEffect)(()=>{let r=!1;return m(null),g(y(e,t,{variant:n})),b(e,t,{variant:n}).then(e=>{r||g(e.url)}),()=>{r=!0}},[t,e,n]),!(_&&t)||p===_?null:(0,S.jsx)(`img`,{alt:c??``,className:s,height:x,onError:e=>{m(_),u?.(e)},src:_,style:{display:`inline-block`,...l},width:v,...d})}export{p as a,l as i,x as n,v as o,u as r,o as s,C as t};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
import{Zp as e}from"./messages-BLwDcEAr.js";import{i as t,t as n}from"./button-C0SPdqvs.js";import{a as r,c as i,i as a,n as o,o as s,r as c,s as l,t as u}from"./dist-BEGLjtzw.js";import{t as d}from"./createLucideIcon-Br0Bd5k2.js";var f=d(`x`,[[`path`,{d:`M18 6 6 18`,key:`1bl5f8`}],[`path`,{d:`m6 6 12 12`,key:`d8bk6v`}]]),p=e();function m({...e}){return(0,p.jsx)(s,{"data-slot":`dialog`,...e})}function h({...e}){return(0,p.jsx)(i,{"data-slot":`dialog-trigger`,...e})}function g({...e}){return(0,p.jsx)(r,{"data-slot":`dialog-portal`,...e})}function _({...e}){return(0,p.jsx)(u,{"data-slot":`dialog-close`,...e})}function v({className:e,...n}){return(0,p.jsx)(a,{className:t(`data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=open]:animate-in`,e),"data-slot":`dialog-overlay`,...n})}function y({className:e,children:n,showCloseButton:r=!0,...i}){return(0,p.jsxs)(g,{"data-slot":`dialog-portal`,children:[(0,p.jsx)(v,{}),(0,p.jsxs)(o,{className:t(`data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg outline-none duration-200 data-[state=closed]:animate-out data-[state=open]:animate-in sm:max-w-lg`,e),"data-slot":`dialog-content`,...i,children:[n,r&&(0,p.jsxs)(u,{className:`absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0`,"data-slot":`dialog-close`,children:[(0,p.jsx)(f,{}),(0,p.jsx)(`span`,{className:`sr-only`,children:`Close`})]})]})]})}function b({className:e,...n}){return(0,p.jsx)(`div`,{className:t(`flex flex-col gap-2 text-center sm:text-left`,e),"data-slot":`dialog-header`,...n})}function x({className:e,showCloseButton:r=!1,children:i,...a}){return(0,p.jsxs)(`div`,{className:t(`flex flex-col-reverse gap-2 sm:flex-row sm:justify-end`,e),"data-slot":`dialog-footer`,...a,children:[i,r&&(0,p.jsx)(u,{asChild:!0,children:(0,p.jsx)(n,{variant:`outline`,children:`Close`})})]})}function S({className:e,...n}){return(0,p.jsx)(l,{className:t(`font-semibold text-lg leading-none`,e),"data-slot":`dialog-title`,...n})}function C({className:e,...n}){return(0,p.jsx)(c,{className:t(`text-muted-foreground text-sm`,e),"data-slot":`dialog-description`,...n})}export{x as a,h as c,C as i,f as l,_ as n,b as o,y as r,S as s,m as t};
|
||||
import{em as e}from"./messages-Bp0bCjzp.js";import{i as t,t as n}from"./button-BgMnOYKD.js";import{a as r,c as i,i as a,n as o,o as s,r as c,s as l,t as u}from"./dist-CPQ-sRtL.js";import{t as d}from"./createLucideIcon-Br0Bd5k2.js";var f=d(`x`,[[`path`,{d:`M18 6 6 18`,key:`1bl5f8`}],[`path`,{d:`m6 6 12 12`,key:`d8bk6v`}]]),p=e();function m({...e}){return(0,p.jsx)(s,{"data-slot":`dialog`,...e})}function h({...e}){return(0,p.jsx)(i,{"data-slot":`dialog-trigger`,...e})}function g({...e}){return(0,p.jsx)(r,{"data-slot":`dialog-portal`,...e})}function _({...e}){return(0,p.jsx)(u,{"data-slot":`dialog-close`,...e})}function v({className:e,...n}){return(0,p.jsx)(a,{className:t(`data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=open]:animate-in`,e),"data-slot":`dialog-overlay`,...n})}function y({className:e,children:n,showCloseButton:r=!0,...i}){return(0,p.jsxs)(g,{"data-slot":`dialog-portal`,children:[(0,p.jsx)(v,{}),(0,p.jsxs)(o,{className:t(`data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg outline-none duration-200 data-[state=closed]:animate-out data-[state=open]:animate-in sm:max-w-lg`,e),"data-slot":`dialog-content`,...i,children:[n,r&&(0,p.jsxs)(u,{className:`absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0`,"data-slot":`dialog-close`,children:[(0,p.jsx)(f,{}),(0,p.jsx)(`span`,{className:`sr-only`,children:`Close`})]})]})]})}function b({className:e,...n}){return(0,p.jsx)(`div`,{className:t(`flex flex-col gap-2 text-center sm:text-left`,e),"data-slot":`dialog-header`,...n})}function x({className:e,showCloseButton:r=!1,children:i,...a}){return(0,p.jsxs)(`div`,{className:t(`flex flex-col-reverse gap-2 sm:flex-row sm:justify-end`,e),"data-slot":`dialog-footer`,...a,children:[i,r&&(0,p.jsx)(u,{asChild:!0,children:(0,p.jsx)(n,{variant:`outline`,children:`Close`})})]})}function S({className:e,...n}){return(0,p.jsx)(l,{className:t(`font-semibold text-lg leading-none`,e),"data-slot":`dialog-title`,...n})}function C({className:e,...n}){return(0,p.jsx)(c,{className:t(`text-muted-foreground text-sm`,e),"data-slot":`dialog-description`,...n})}export{x as a,h as c,C as i,f as l,_ as n,b as o,y as r,S as s,m as t};
|
||||
@@ -0,0 +1 @@
|
||||
import{an as e,cn as t,dn as n,em as r,fn as i,gn as a,hn as o,ln as s,mn as c,on as l,pn as u,sn as d,un as f}from"./messages-Bp0bCjzp.js";import{t as p}from"./button-BgMnOYKD.js";import{t as m}from"./checkbox-CKrZFk1G.js";import{c as h,i as g,s as _}from"./zod-rwFXxHlg.js";import{a as v,c as y,i as b,l as x,n as S,o as C,r as w,s as T,t as E}from"./form-BhKmyN6D.js";import{t as D}from"./page-header-CDwG3nxs.js";import{t as O}from"./show-submitted-data-C8ocsjDF.js";var k=r(),A=[{id:`recents`,label:c()},{id:`home`,label:u()},{id:`applications`,label:i()},{id:`desktop`,label:n()},{id:`downloads`,label:f()},{id:`documents`,label:s()}],j=_({items:g(h()).refine(e=>e.some(e=>e),{message:t()})}),M={items:[`recents`,`home`]};function N(){let t=x({resolver:y(j),defaultValues:M});return(0,k.jsx)(E,{...t,children:(0,k.jsxs)(`form`,{className:`space-y-8`,onSubmit:t.handleSubmit(e=>O(e)),children:[(0,k.jsx)(b,{control:t.control,name:`items`,render:()=>(0,k.jsxs)(v,{children:[(0,k.jsxs)(`div`,{className:`mb-4`,children:[(0,k.jsx)(C,{className:`text-base`,children:d()}),(0,k.jsx)(w,{children:l()})]}),A.map(e=>(0,k.jsx)(b,{control:t.control,name:`items`,render:({field:t})=>(0,k.jsxs)(v,{className:`flex flex-row items-start`,children:[(0,k.jsx)(S,{children:(0,k.jsx)(m,{checked:t.value?.includes(e.id),onCheckedChange:n=>n?t.onChange([...t.value,e.id]):t.onChange(t.value?.filter(t=>t!==e.id))})}),(0,k.jsx)(C,{className:`font-normal`,children:e.label})]},e.id)},e.id)),(0,k.jsx)(T,{})]})}),(0,k.jsx)(p,{type:`submit`,children:e()})]})})}function P(){return(0,k.jsx)(D,{description:o(),title:a(),variant:`section`,children:(0,k.jsx)(N,{})})}var F=P;export{F as component};
|
||||
@@ -1 +0,0 @@
|
||||
import{Zp as e,an as t,cn as n,dn as r,fn as i,gn as a,hn as o,ln as s,mn as c,on as l,pn as u,sn as d,un as f}from"./messages-BLwDcEAr.js";import{t as p}from"./button-C0SPdqvs.js";import{t as m}from"./checkbox-BWQ1sC3Y.js";import{c as h,i as g,s as _}from"./zod-rwFXxHlg.js";import{a as v,c as y,i as b,l as x,n as S,o as C,r as w,s as T,t as E}from"./form-fulEvgfz.js";import{t as D}from"./page-header-5Y8t-L2D.js";import{t as O}from"./show-submitted-data-COdV4uz4.js";var k=e(),A=[{id:`recents`,label:c()},{id:`home`,label:u()},{id:`applications`,label:i()},{id:`desktop`,label:r()},{id:`downloads`,label:f()},{id:`documents`,label:s()}],j=_({items:g(h()).refine(e=>e.some(e=>e),{message:n()})}),M={items:[`recents`,`home`]};function N(){let e=x({resolver:y(j),defaultValues:M});return(0,k.jsx)(E,{...e,children:(0,k.jsxs)(`form`,{className:`space-y-8`,onSubmit:e.handleSubmit(e=>O(e)),children:[(0,k.jsx)(b,{control:e.control,name:`items`,render:()=>(0,k.jsxs)(v,{children:[(0,k.jsxs)(`div`,{className:`mb-4`,children:[(0,k.jsx)(C,{className:`text-base`,children:d()}),(0,k.jsx)(w,{children:l()})]}),A.map(t=>(0,k.jsx)(b,{control:e.control,name:`items`,render:({field:e})=>(0,k.jsxs)(v,{className:`flex flex-row items-start`,children:[(0,k.jsx)(S,{children:(0,k.jsx)(m,{checked:e.value?.includes(t.id),onCheckedChange:n=>n?e.onChange([...e.value,t.id]):e.onChange(e.value?.filter(e=>e!==t.id))})}),(0,k.jsx)(C,{className:`font-normal`,children:t.label})]},t.id)},t.id)),(0,k.jsx)(T,{})]})}),(0,k.jsx)(p,{type:`submit`,children:t()})]})})}function P(){return(0,k.jsx)(D,{description:o(),title:a(),variant:`section`,children:(0,k.jsx)(N,{})})}var F=P;export{F as component};
|
||||
@@ -1 +1 @@
|
||||
import{r as e}from"./chunk-DECur_0Z.js";import{t}from"./react-CO2uhaBc.js";import{Zp as n}from"./messages-BLwDcEAr.js";import{t as r}from"./dist-XiAcd8rT.js";import{u as i}from"./button-C0SPdqvs.js";import{a,r as o,t as s}from"./dist-D343v8gR.js";import{t as c}from"./dist-C9d8iJDv.js";import{n as l}from"./dist-CYQVWHzW.js";import{t as u}from"./dist-BixeH3nX.js";import{t as d}from"./dist-DJTK1B2D.js";import{n as f,r as p,t as m}from"./dist-DNb3IZ7I.js";var h=e(t(),1),g=n(),_=`Radio`,[v,y]=a(_),[b,x]=v(_),S=h.forwardRef((e,t)=>{let{__scopeRadio:n,name:a,checked:s=!1,required:c,disabled:l,value:u=`on`,onCheck:d,form:f,...p}=e,[m,_]=h.useState(null),v=i(t,e=>_(e)),y=h.useRef(!1),x=m?f||!!m.closest(`form`):!0;return(0,g.jsxs)(b,{scope:n,checked:s,disabled:l,children:[(0,g.jsx)(r.button,{type:`button`,role:`radio`,"aria-checked":s,"data-state":D(s),"data-disabled":l?``:void 0,disabled:l,value:u,...p,ref:v,onClick:o(e.onClick,e=>{s||d?.(),x&&(y.current=e.isPropagationStopped(),y.current||e.stopPropagation())})}),x&&(0,g.jsx)(E,{control:m,bubbles:!y.current,name:a,value:u,checked:s,required:c,disabled:l,form:f,style:{transform:`translateX(-100%)`}})]})});S.displayName=_;var C=`RadioIndicator`,w=h.forwardRef((e,t)=>{let{__scopeRadio:n,forceMount:i,...a}=e,o=x(C,n);return(0,g.jsx)(c,{present:i||o.checked,children:(0,g.jsx)(r.span,{"data-state":D(o.checked),"data-disabled":o.disabled?``:void 0,...a,ref:t})})});w.displayName=C;var T=`RadioBubbleInput`,E=h.forwardRef(({__scopeRadio:e,control:t,checked:n,bubbles:a=!0,...o},s)=>{let c=h.useRef(null),l=i(c,s),f=u(n),p=d(t);return h.useEffect(()=>{let e=c.current;if(!e)return;let t=window.HTMLInputElement.prototype,r=Object.getOwnPropertyDescriptor(t,`checked`).set;if(f!==n&&r){let t=new Event(`click`,{bubbles:a});r.call(e,n),e.dispatchEvent(t)}},[f,n,a]),(0,g.jsx)(r.input,{type:`radio`,"aria-hidden":!0,defaultChecked:n,...o,tabIndex:-1,ref:l,style:{...o.style,...p,position:`absolute`,pointerEvents:`none`,opacity:0,margin:0}})});E.displayName=T;function D(e){return e?`checked`:`unchecked`}var O=[`ArrowUp`,`ArrowDown`,`ArrowLeft`,`ArrowRight`],k=`RadioGroup`,[A,j]=a(k,[p,y]),M=p(),N=y(),[P,F]=A(k),I=h.forwardRef((e,t)=>{let{__scopeRadioGroup:n,name:i,defaultValue:a,value:o,required:c=!1,disabled:u=!1,orientation:d,dir:p,loop:m=!0,onValueChange:h,..._}=e,v=M(n),y=l(p),[b,x]=s({prop:o,defaultProp:a??null,onChange:h,caller:k});return(0,g.jsx)(P,{scope:n,name:i,required:c,disabled:u,value:b,onValueChange:x,children:(0,g.jsx)(f,{asChild:!0,...v,orientation:d,dir:y,loop:m,children:(0,g.jsx)(r.div,{role:`radiogroup`,"aria-required":c,"aria-orientation":d,"data-disabled":u?``:void 0,dir:y,..._,ref:t})})})});I.displayName=k;var L=`RadioGroupItem`,R=h.forwardRef((e,t)=>{let{__scopeRadioGroup:n,disabled:r,...a}=e,s=F(L,n),c=s.disabled||r,l=M(n),u=N(n),d=h.useRef(null),f=i(t,d),p=s.value===a.value,_=h.useRef(!1);return h.useEffect(()=>{let e=e=>{O.includes(e.key)&&(_.current=!0)},t=()=>_.current=!1;return document.addEventListener(`keydown`,e),document.addEventListener(`keyup`,t),()=>{document.removeEventListener(`keydown`,e),document.removeEventListener(`keyup`,t)}},[]),(0,g.jsx)(m,{asChild:!0,...l,focusable:!c,active:p,children:(0,g.jsx)(S,{disabled:c,required:s.required,checked:p,...u,...a,name:s.name,ref:f,onCheck:()=>s.onValueChange(a.value),onKeyDown:o(e=>{e.key===`Enter`&&e.preventDefault()}),onFocus:o(a.onFocus,()=>{_.current&&d.current?.click()})})})});R.displayName=L;var z=`RadioGroupIndicator`,B=h.forwardRef((e,t)=>{let{__scopeRadioGroup:n,...r}=e;return(0,g.jsx)(w,{...N(n),...r,ref:t})});B.displayName=z;var V=I,H=R,U=B;export{H as n,V as r,U as t};
|
||||
import{r as e}from"./chunk-DECur_0Z.js";import{t}from"./react-CO2uhaBc.js";import{em as n}from"./messages-Bp0bCjzp.js";import{t as r}from"./dist-BFnxq45V.js";import{u as i}from"./button-BgMnOYKD.js";import{a,r as o,t as s}from"./dist-CKFLmh1A.js";import{t as c}from"./dist-Clua1vrx.js";import{n as l}from"./dist-C97YBjnT.js";import{t as u}from"./dist-BixeH3nX.js";import{t as d}from"./dist-DGDEBCW9.js";import{n as f,r as p,t as m}from"./dist-D7AUoOWu.js";var h=e(t(),1),g=n(),_=`Radio`,[v,y]=a(_),[b,x]=v(_),S=h.forwardRef((e,t)=>{let{__scopeRadio:n,name:a,checked:s=!1,required:c,disabled:l,value:u=`on`,onCheck:d,form:f,...p}=e,[m,_]=h.useState(null),v=i(t,e=>_(e)),y=h.useRef(!1),x=m?f||!!m.closest(`form`):!0;return(0,g.jsxs)(b,{scope:n,checked:s,disabled:l,children:[(0,g.jsx)(r.button,{type:`button`,role:`radio`,"aria-checked":s,"data-state":D(s),"data-disabled":l?``:void 0,disabled:l,value:u,...p,ref:v,onClick:o(e.onClick,e=>{s||d?.(),x&&(y.current=e.isPropagationStopped(),y.current||e.stopPropagation())})}),x&&(0,g.jsx)(E,{control:m,bubbles:!y.current,name:a,value:u,checked:s,required:c,disabled:l,form:f,style:{transform:`translateX(-100%)`}})]})});S.displayName=_;var C=`RadioIndicator`,w=h.forwardRef((e,t)=>{let{__scopeRadio:n,forceMount:i,...a}=e,o=x(C,n);return(0,g.jsx)(c,{present:i||o.checked,children:(0,g.jsx)(r.span,{"data-state":D(o.checked),"data-disabled":o.disabled?``:void 0,...a,ref:t})})});w.displayName=C;var T=`RadioBubbleInput`,E=h.forwardRef(({__scopeRadio:e,control:t,checked:n,bubbles:a=!0,...o},s)=>{let c=h.useRef(null),l=i(c,s),f=u(n),p=d(t);return h.useEffect(()=>{let e=c.current;if(!e)return;let t=window.HTMLInputElement.prototype,r=Object.getOwnPropertyDescriptor(t,`checked`).set;if(f!==n&&r){let t=new Event(`click`,{bubbles:a});r.call(e,n),e.dispatchEvent(t)}},[f,n,a]),(0,g.jsx)(r.input,{type:`radio`,"aria-hidden":!0,defaultChecked:n,...o,tabIndex:-1,ref:l,style:{...o.style,...p,position:`absolute`,pointerEvents:`none`,opacity:0,margin:0}})});E.displayName=T;function D(e){return e?`checked`:`unchecked`}var O=[`ArrowUp`,`ArrowDown`,`ArrowLeft`,`ArrowRight`],k=`RadioGroup`,[A,j]=a(k,[p,y]),M=p(),N=y(),[P,F]=A(k),I=h.forwardRef((e,t)=>{let{__scopeRadioGroup:n,name:i,defaultValue:a,value:o,required:c=!1,disabled:u=!1,orientation:d,dir:p,loop:m=!0,onValueChange:h,..._}=e,v=M(n),y=l(p),[b,x]=s({prop:o,defaultProp:a??null,onChange:h,caller:k});return(0,g.jsx)(P,{scope:n,name:i,required:c,disabled:u,value:b,onValueChange:x,children:(0,g.jsx)(f,{asChild:!0,...v,orientation:d,dir:y,loop:m,children:(0,g.jsx)(r.div,{role:`radiogroup`,"aria-required":c,"aria-orientation":d,"data-disabled":u?``:void 0,dir:y,..._,ref:t})})})});I.displayName=k;var L=`RadioGroupItem`,R=h.forwardRef((e,t)=>{let{__scopeRadioGroup:n,disabled:r,...a}=e,s=F(L,n),c=s.disabled||r,l=M(n),u=N(n),d=h.useRef(null),f=i(t,d),p=s.value===a.value,_=h.useRef(!1);return h.useEffect(()=>{let e=e=>{O.includes(e.key)&&(_.current=!0)},t=()=>_.current=!1;return document.addEventListener(`keydown`,e),document.addEventListener(`keyup`,t),()=>{document.removeEventListener(`keydown`,e),document.removeEventListener(`keyup`,t)}},[]),(0,g.jsx)(m,{asChild:!0,...l,focusable:!c,active:p,children:(0,g.jsx)(S,{disabled:c,required:s.required,checked:p,...u,...a,name:s.name,ref:f,onCheck:()=>s.onValueChange(a.value),onKeyDown:o(e=>{e.key===`Enter`&&e.preventDefault()}),onFocus:o(a.onFocus,()=>{_.current&&d.current?.click()})})})});R.displayName=L;var z=`RadioGroupIndicator`,B=h.forwardRef((e,t)=>{let{__scopeRadioGroup:n,...r}=e;return(0,g.jsx)(w,{...N(n),...r,ref:t})});B.displayName=z;var V=I,H=R,U=B;export{H as n,V as r,U as t};
|
||||
@@ -1 +1 @@
|
||||
import{r as e}from"./chunk-DECur_0Z.js";import{t}from"./react-CO2uhaBc.js";import{n}from"./dist-D343v8gR.js";var r=e(t(),1),i=r.useId||(()=>void 0),a=0;function o(e){let[t,o]=r.useState(i());return n(()=>{e||o(e=>e??String(a++))},[e]),e||(t?`radix-${t}`:``)}function s(e){let t=r.useRef(e);return r.useEffect(()=>{t.current=e}),r.useMemo(()=>(...e)=>t.current?.(...e),[])}export{o as n,s as t};
|
||||
import{r as e}from"./chunk-DECur_0Z.js";import{t}from"./react-CO2uhaBc.js";import{n}from"./dist-CKFLmh1A.js";var r=e(t(),1),i=r.useId||(()=>void 0),a=0;function o(e){let[t,o]=r.useState(i());return n(()=>{e||o(e=>e??String(a++))},[e]),e||(t?`radix-${t}`:``)}function s(e){let t=r.useRef(e);return r.useEffect(()=>{t.current=e}),r.useMemo(()=>(...e)=>t.current?.(...e),[])}export{o as n,s as t};
|
||||
@@ -1 +1 @@
|
||||
import{r as e,t}from"./chunk-DECur_0Z.js";import{t as n}from"./react-CO2uhaBc.js";import{Zp as r}from"./messages-BLwDcEAr.js";import{s as i}from"./button-C0SPdqvs.js";var a=t((e=>{var t=n();function r(e){var t=`https://react.dev/errors/`+e;if(1<arguments.length){t+=`?args[]=`+encodeURIComponent(arguments[1]);for(var n=2;n<arguments.length;n++)t+=`&args[]=`+encodeURIComponent(arguments[n])}return`Minified React error #`+e+`; visit `+t+` for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`}function i(){}var a={d:{f:i,r:function(){throw Error(r(522))},D:i,C:i,L:i,m:i,X:i,S:i,M:i},p:0,findDOMNode:null},o=Symbol.for(`react.portal`);function s(e,t,n){var r=3<arguments.length&&arguments[3]!==void 0?arguments[3]:null;return{$$typeof:o,key:r==null?null:``+r,children:e,containerInfo:t,implementation:n}}var c=t.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;function l(e,t){if(e===`font`)return``;if(typeof t==`string`)return t===`use-credentials`?t:``}e.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=a,e.createPortal=function(e,t){var n=2<arguments.length&&arguments[2]!==void 0?arguments[2]:null;if(!t||t.nodeType!==1&&t.nodeType!==9&&t.nodeType!==11)throw Error(r(299));return s(e,t,null,n)},e.flushSync=function(e){var t=c.T,n=a.p;try{if(c.T=null,a.p=2,e)return e()}finally{c.T=t,a.p=n,a.d.f()}},e.preconnect=function(e,t){typeof e==`string`&&(t?(t=t.crossOrigin,t=typeof t==`string`?t===`use-credentials`?t:``:void 0):t=null,a.d.C(e,t))},e.prefetchDNS=function(e){typeof e==`string`&&a.d.D(e)},e.preinit=function(e,t){if(typeof e==`string`&&t&&typeof t.as==`string`){var n=t.as,r=l(n,t.crossOrigin),i=typeof t.integrity==`string`?t.integrity:void 0,o=typeof t.fetchPriority==`string`?t.fetchPriority:void 0;n===`style`?a.d.S(e,typeof t.precedence==`string`?t.precedence:void 0,{crossOrigin:r,integrity:i,fetchPriority:o}):n===`script`&&a.d.X(e,{crossOrigin:r,integrity:i,fetchPriority:o,nonce:typeof t.nonce==`string`?t.nonce:void 0})}},e.preinitModule=function(e,t){if(typeof e==`string`)if(typeof t==`object`&&t){if(t.as==null||t.as===`script`){var n=l(t.as,t.crossOrigin);a.d.M(e,{crossOrigin:n,integrity:typeof t.integrity==`string`?t.integrity:void 0,nonce:typeof t.nonce==`string`?t.nonce:void 0})}}else t??a.d.M(e)},e.preload=function(e,t){if(typeof e==`string`&&typeof t==`object`&&t&&typeof t.as==`string`){var n=t.as,r=l(n,t.crossOrigin);a.d.L(e,n,{crossOrigin:r,integrity:typeof t.integrity==`string`?t.integrity:void 0,nonce:typeof t.nonce==`string`?t.nonce:void 0,type:typeof t.type==`string`?t.type:void 0,fetchPriority:typeof t.fetchPriority==`string`?t.fetchPriority:void 0,referrerPolicy:typeof t.referrerPolicy==`string`?t.referrerPolicy:void 0,imageSrcSet:typeof t.imageSrcSet==`string`?t.imageSrcSet:void 0,imageSizes:typeof t.imageSizes==`string`?t.imageSizes:void 0,media:typeof t.media==`string`?t.media:void 0})}},e.preloadModule=function(e,t){if(typeof e==`string`)if(t){var n=l(t.as,t.crossOrigin);a.d.m(e,{as:typeof t.as==`string`&&t.as!==`script`?t.as:void 0,crossOrigin:n,integrity:typeof t.integrity==`string`?t.integrity:void 0})}else a.d.m(e)},e.requestFormReset=function(e){a.d.r(e)},e.unstable_batchedUpdates=function(e,t){return e(t)},e.useFormState=function(e,t,n){return c.H.useFormState(e,t,n)},e.useFormStatus=function(){return c.H.useHostTransitionStatus()},e.version=`19.2.5`})),o=t(((e,t)=>{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=a()})),s=e(n(),1),c=e(o(),1),l=r(),u=[`a`,`button`,`div`,`form`,`h2`,`h3`,`img`,`input`,`label`,`li`,`nav`,`ol`,`p`,`select`,`span`,`svg`,`ul`].reduce((e,t)=>{let n=i(`Primitive.${t}`),r=s.forwardRef((e,r)=>{let{asChild:i,...a}=e,o=i?n:t;return typeof window<`u`&&(window[Symbol.for(`radix-ui`)]=!0),(0,l.jsx)(o,{...a,ref:r})});return r.displayName=`Primitive.${t}`,{...e,[t]:r}},{});function d(e,t){e&&c.flushSync(()=>e.dispatchEvent(t))}export{d as n,o as r,u as t};
|
||||
import{r as e,t}from"./chunk-DECur_0Z.js";import{t as n}from"./react-CO2uhaBc.js";import{em as r}from"./messages-Bp0bCjzp.js";import{s as i}from"./button-BgMnOYKD.js";var a=t((e=>{var t=n();function r(e){var t=`https://react.dev/errors/`+e;if(1<arguments.length){t+=`?args[]=`+encodeURIComponent(arguments[1]);for(var n=2;n<arguments.length;n++)t+=`&args[]=`+encodeURIComponent(arguments[n])}return`Minified React error #`+e+`; visit `+t+` for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`}function i(){}var a={d:{f:i,r:function(){throw Error(r(522))},D:i,C:i,L:i,m:i,X:i,S:i,M:i},p:0,findDOMNode:null},o=Symbol.for(`react.portal`);function s(e,t,n){var r=3<arguments.length&&arguments[3]!==void 0?arguments[3]:null;return{$$typeof:o,key:r==null?null:``+r,children:e,containerInfo:t,implementation:n}}var c=t.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;function l(e,t){if(e===`font`)return``;if(typeof t==`string`)return t===`use-credentials`?t:``}e.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=a,e.createPortal=function(e,t){var n=2<arguments.length&&arguments[2]!==void 0?arguments[2]:null;if(!t||t.nodeType!==1&&t.nodeType!==9&&t.nodeType!==11)throw Error(r(299));return s(e,t,null,n)},e.flushSync=function(e){var t=c.T,n=a.p;try{if(c.T=null,a.p=2,e)return e()}finally{c.T=t,a.p=n,a.d.f()}},e.preconnect=function(e,t){typeof e==`string`&&(t?(t=t.crossOrigin,t=typeof t==`string`?t===`use-credentials`?t:``:void 0):t=null,a.d.C(e,t))},e.prefetchDNS=function(e){typeof e==`string`&&a.d.D(e)},e.preinit=function(e,t){if(typeof e==`string`&&t&&typeof t.as==`string`){var n=t.as,r=l(n,t.crossOrigin),i=typeof t.integrity==`string`?t.integrity:void 0,o=typeof t.fetchPriority==`string`?t.fetchPriority:void 0;n===`style`?a.d.S(e,typeof t.precedence==`string`?t.precedence:void 0,{crossOrigin:r,integrity:i,fetchPriority:o}):n===`script`&&a.d.X(e,{crossOrigin:r,integrity:i,fetchPriority:o,nonce:typeof t.nonce==`string`?t.nonce:void 0})}},e.preinitModule=function(e,t){if(typeof e==`string`)if(typeof t==`object`&&t){if(t.as==null||t.as===`script`){var n=l(t.as,t.crossOrigin);a.d.M(e,{crossOrigin:n,integrity:typeof t.integrity==`string`?t.integrity:void 0,nonce:typeof t.nonce==`string`?t.nonce:void 0})}}else t??a.d.M(e)},e.preload=function(e,t){if(typeof e==`string`&&typeof t==`object`&&t&&typeof t.as==`string`){var n=t.as,r=l(n,t.crossOrigin);a.d.L(e,n,{crossOrigin:r,integrity:typeof t.integrity==`string`?t.integrity:void 0,nonce:typeof t.nonce==`string`?t.nonce:void 0,type:typeof t.type==`string`?t.type:void 0,fetchPriority:typeof t.fetchPriority==`string`?t.fetchPriority:void 0,referrerPolicy:typeof t.referrerPolicy==`string`?t.referrerPolicy:void 0,imageSrcSet:typeof t.imageSrcSet==`string`?t.imageSrcSet:void 0,imageSizes:typeof t.imageSizes==`string`?t.imageSizes:void 0,media:typeof t.media==`string`?t.media:void 0})}},e.preloadModule=function(e,t){if(typeof e==`string`)if(t){var n=l(t.as,t.crossOrigin);a.d.m(e,{as:typeof t.as==`string`&&t.as!==`script`?t.as:void 0,crossOrigin:n,integrity:typeof t.integrity==`string`?t.integrity:void 0})}else a.d.m(e)},e.requestFormReset=function(e){a.d.r(e)},e.unstable_batchedUpdates=function(e,t){return e(t)},e.useFormState=function(e,t,n){return c.H.useFormState(e,t,n)},e.useFormStatus=function(){return c.H.useHostTransitionStatus()},e.version=`19.2.5`})),o=t(((e,t)=>{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=a()})),s=e(n(),1),c=e(o(),1),l=r(),u=[`a`,`button`,`div`,`form`,`h2`,`h3`,`img`,`input`,`label`,`li`,`nav`,`ol`,`p`,`select`,`span`,`svg`,`ul`].reduce((e,t)=>{let n=i(`Primitive.${t}`),r=s.forwardRef((e,r)=>{let{asChild:i,...a}=e,o=i?n:t;return typeof window<`u`&&(window[Symbol.for(`radix-ui`)]=!0),(0,l.jsx)(o,{...a,ref:r})});return r.displayName=`Primitive.${t}`,{...e,[t]:r}},{});function d(e,t){e&&c.flushSync(()=>e.dispatchEvent(t))}export{d as n,o as r,u as t};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
import{r as e}from"./chunk-DECur_0Z.js";import{t}from"./react-CO2uhaBc.js";import{Zp as n}from"./messages-BLwDcEAr.js";import{t as r}from"./dist-XiAcd8rT.js";var i=e(t(),1),a=n(),o=Object.freeze({position:`absolute`,border:0,width:1,height:1,padding:0,margin:-1,overflow:`hidden`,clip:`rect(0, 0, 0, 0)`,whiteSpace:`nowrap`,wordWrap:`normal`}),s=`VisuallyHidden`,c=i.forwardRef((e,t)=>(0,a.jsx)(r.span,{...e,ref:t,style:{...o,...e.style}}));c.displayName=s;var l=c;export{o as n,l as t};
|
||||
import{r as e}from"./chunk-DECur_0Z.js";import{t}from"./react-CO2uhaBc.js";import{em as n}from"./messages-Bp0bCjzp.js";import{t as r}from"./dist-BFnxq45V.js";var i=e(t(),1),a=n(),o=Object.freeze({position:`absolute`,border:0,width:1,height:1,padding:0,margin:-1,overflow:`hidden`,clip:`rect(0, 0, 0, 0)`,whiteSpace:`nowrap`,wordWrap:`normal`}),s=`VisuallyHidden`,c=i.forwardRef((e,t)=>(0,a.jsx)(r.span,{...e,ref:t,style:{...o,...e.style}}));c.displayName=s;var l=c;export{o as n,l as t};
|
||||
@@ -1 +1 @@
|
||||
import{r as e}from"./chunk-DECur_0Z.js";import{t}from"./react-CO2uhaBc.js";import{Zp as n}from"./messages-BLwDcEAr.js";import{s as r,u as i}from"./button-C0SPdqvs.js";import{a}from"./dist-D343v8gR.js";var o=e(t(),1),s=n();function c(e){let t=e+`CollectionProvider`,[n,c]=a(t),[l,u]=n(t,{collectionRef:{current:null},itemMap:new Map}),d=e=>{let{scope:t,children:n}=e,r=o.useRef(null),i=o.useRef(new Map).current;return(0,s.jsx)(l,{scope:t,itemMap:i,collectionRef:r,children:n})};d.displayName=t;let f=e+`CollectionSlot`,p=r(f),m=o.forwardRef((e,t)=>{let{scope:n,children:r}=e;return(0,s.jsx)(p,{ref:i(t,u(f,n).collectionRef),children:r})});m.displayName=f;let h=e+`CollectionItemSlot`,g=`data-radix-collection-item`,_=r(h),v=o.forwardRef((e,t)=>{let{scope:n,children:r,...a}=e,c=o.useRef(null),l=i(t,c),d=u(h,n);return o.useEffect(()=>(d.itemMap.set(c,{ref:c,...a}),()=>void d.itemMap.delete(c))),(0,s.jsx)(_,{[g]:``,ref:l,children:r})});v.displayName=h;function y(t){let n=u(e+`CollectionConsumer`,t);return o.useCallback(()=>{let e=n.collectionRef.current;if(!e)return[];let t=Array.from(e.querySelectorAll(`[${g}]`));return Array.from(n.itemMap.values()).sort((e,n)=>t.indexOf(e.ref.current)-t.indexOf(n.ref.current))},[n.collectionRef,n.itemMap])}return[{Provider:d,Slot:m,ItemSlot:v},y,c]}export{c as t};
|
||||
import{r as e}from"./chunk-DECur_0Z.js";import{t}from"./react-CO2uhaBc.js";import{em as n}from"./messages-Bp0bCjzp.js";import{s as r,u as i}from"./button-BgMnOYKD.js";import{a}from"./dist-CKFLmh1A.js";var o=e(t(),1),s=n();function c(e){let t=e+`CollectionProvider`,[n,c]=a(t),[l,u]=n(t,{collectionRef:{current:null},itemMap:new Map}),d=e=>{let{scope:t,children:n}=e,r=o.useRef(null),i=o.useRef(new Map).current;return(0,s.jsx)(l,{scope:t,itemMap:i,collectionRef:r,children:n})};d.displayName=t;let f=e+`CollectionSlot`,p=r(f),m=o.forwardRef((e,t)=>{let{scope:n,children:r}=e;return(0,s.jsx)(p,{ref:i(t,u(f,n).collectionRef),children:r})});m.displayName=f;let h=e+`CollectionItemSlot`,g=`data-radix-collection-item`,_=r(h),v=o.forwardRef((e,t)=>{let{scope:n,children:r,...a}=e,c=o.useRef(null),l=i(t,c),d=u(h,n);return o.useEffect(()=>(d.itemMap.set(c,{ref:c,...a}),()=>void d.itemMap.delete(c))),(0,s.jsx)(_,{[g]:``,ref:l,children:r})});v.displayName=h;function y(t){let n=u(e+`CollectionConsumer`,t);return o.useCallback(()=>{let e=n.collectionRef.current;if(!e)return[];let t=Array.from(e.querySelectorAll(`[${g}]`));return Array.from(n.itemMap.values()).sort((e,n)=>t.indexOf(e.ref.current)-t.indexOf(n.ref.current))},[n.collectionRef,n.itemMap])}return[{Provider:d,Slot:m,ItemSlot:v},y,c]}export{c as t};
|
||||
@@ -1 +1 @@
|
||||
import{r as e}from"./chunk-DECur_0Z.js";import{t}from"./react-CO2uhaBc.js";import{Zp as n}from"./messages-BLwDcEAr.js";var r=e(t(),1),i=n(),a=r.createContext(void 0),o=e=>{let{dir:t,children:n}=e;return(0,i.jsx)(a.Provider,{value:t,children:n})};function s(e){let t=r.useContext(a);return e||t||`ltr`}export{s as n,o as t};
|
||||
import{r as e}from"./chunk-DECur_0Z.js";import{t}from"./react-CO2uhaBc.js";import{em as n}from"./messages-Bp0bCjzp.js";var r=e(t(),1),i=n(),a=r.createContext(void 0),o=e=>{let{dir:t,children:n}=e;return(0,i.jsx)(a.Provider,{value:t,children:n})};function s(e){let t=r.useContext(a);return e||t||`ltr`}export{s as n,o as t};
|
||||
@@ -1 +1 @@
|
||||
import{r as e}from"./chunk-DECur_0Z.js";import{t}from"./react-CO2uhaBc.js";import{Zp as n}from"./messages-BLwDcEAr.js";var r=e(t(),1),i=n();function a(e,t){let n=r.createContext(t),a=e=>{let{children:t,...a}=e,o=r.useMemo(()=>a,Object.values(a));return(0,i.jsx)(n.Provider,{value:o,children:t})};a.displayName=e+`Provider`;function o(i){let a=r.useContext(n);if(a)return a;if(t!==void 0)return t;throw Error(`\`${i}\` must be used within \`${e}\``)}return[a,o]}function o(e,t=[]){let n=[];function a(t,a){let o=r.createContext(a),s=n.length;n=[...n,a];let c=t=>{let{scope:n,children:a,...c}=t,l=n?.[e]?.[s]||o,u=r.useMemo(()=>c,Object.values(c));return(0,i.jsx)(l.Provider,{value:u,children:a})};c.displayName=t+`Provider`;function l(n,i){let c=i?.[e]?.[s]||o,l=r.useContext(c);if(l)return l;if(a!==void 0)return a;throw Error(`\`${n}\` must be used within \`${t}\``)}return[c,l]}let o=()=>{let t=n.map(e=>r.createContext(e));return function(n){let i=n?.[e]||t;return r.useMemo(()=>({[`__scope${e}`]:{...n,[e]:i}}),[n,i])}};return o.scopeName=e,[a,s(o,...t)]}function s(...e){let t=e[0];if(e.length===1)return t;let n=()=>{let n=e.map(e=>({useScope:e(),scopeName:e.scopeName}));return function(e){let i=n.reduce((t,{useScope:n,scopeName:r})=>{let i=n(e)[`__scope${r}`];return{...t,...i}},{});return r.useMemo(()=>({[`__scope${t.scopeName}`]:i}),[i])}};return n.scopeName=t.scopeName,n}typeof window<`u`&&window.document&&window.document.createElement;function c(e,t,{checkForDefaultPrevented:n=!0}={}){return function(r){if(e?.(r),n===!1||!r.defaultPrevented)return t?.(r)}}var l=globalThis?.document?r.useLayoutEffect:()=>{},u=r.useInsertionEffect||l;function d({prop:e,defaultProp:t,onChange:n=()=>{},caller:i}){let[a,o,s]=f({defaultProp:t,onChange:n}),c=e!==void 0,l=c?e:a;{let t=r.useRef(e!==void 0);r.useEffect(()=>{let e=t.current;e!==c&&console.warn(`${i} is changing from ${e?`controlled`:`uncontrolled`} to ${c?`controlled`:`uncontrolled`}. Components should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled value for the lifetime of the component.`),t.current=c},[c,i])}return[l,r.useCallback(t=>{if(c){let n=p(t)?t(e):t;n!==e&&s.current?.(n)}else o(t)},[c,e,o,s])]}function f({defaultProp:e,onChange:t}){let[n,i]=r.useState(e),a=r.useRef(n),o=r.useRef(t);return u(()=>{o.current=t},[t]),r.useEffect(()=>{a.current!==n&&(o.current?.(n),a.current=n)},[n,a]),[n,i,o]}function p(e){return typeof e==`function`}export{o as a,a as i,l as n,c as r,d as t};
|
||||
import{r as e}from"./chunk-DECur_0Z.js";import{t}from"./react-CO2uhaBc.js";import{em as n}from"./messages-Bp0bCjzp.js";var r=e(t(),1),i=n();function a(e,t){let n=r.createContext(t),a=e=>{let{children:t,...a}=e,o=r.useMemo(()=>a,Object.values(a));return(0,i.jsx)(n.Provider,{value:o,children:t})};a.displayName=e+`Provider`;function o(i){let a=r.useContext(n);if(a)return a;if(t!==void 0)return t;throw Error(`\`${i}\` must be used within \`${e}\``)}return[a,o]}function o(e,t=[]){let n=[];function a(t,a){let o=r.createContext(a),s=n.length;n=[...n,a];let c=t=>{let{scope:n,children:a,...c}=t,l=n?.[e]?.[s]||o,u=r.useMemo(()=>c,Object.values(c));return(0,i.jsx)(l.Provider,{value:u,children:a})};c.displayName=t+`Provider`;function l(n,i){let c=i?.[e]?.[s]||o,l=r.useContext(c);if(l)return l;if(a!==void 0)return a;throw Error(`\`${n}\` must be used within \`${t}\``)}return[c,l]}let o=()=>{let t=n.map(e=>r.createContext(e));return function(n){let i=n?.[e]||t;return r.useMemo(()=>({[`__scope${e}`]:{...n,[e]:i}}),[n,i])}};return o.scopeName=e,[a,s(o,...t)]}function s(...e){let t=e[0];if(e.length===1)return t;let n=()=>{let n=e.map(e=>({useScope:e(),scopeName:e.scopeName}));return function(e){let i=n.reduce((t,{useScope:n,scopeName:r})=>{let i=n(e)[`__scope${r}`];return{...t,...i}},{});return r.useMemo(()=>({[`__scope${t.scopeName}`]:i}),[i])}};return n.scopeName=t.scopeName,n}typeof window<`u`&&window.document&&window.document.createElement;function c(e,t,{checkForDefaultPrevented:n=!0}={}){return function(r){if(e?.(r),n===!1||!r.defaultPrevented)return t?.(r)}}var l=globalThis?.document?r.useLayoutEffect:()=>{},u=r.useInsertionEffect||l;function d({prop:e,defaultProp:t,onChange:n=()=>{},caller:i}){let[a,o,s]=f({defaultProp:t,onChange:n}),c=e!==void 0,l=c?e:a;{let t=r.useRef(e!==void 0);r.useEffect(()=>{let e=t.current;e!==c&&console.warn(`${i} is changing from ${e?`controlled`:`uncontrolled`} to ${c?`controlled`:`uncontrolled`}. Components should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled value for the lifetime of the component.`),t.current=c},[c,i])}return[l,r.useCallback(t=>{if(c){let n=p(t)?t(e):t;n!==e&&s.current?.(n)}else o(t)},[c,e,o,s])]}function f({defaultProp:e,onChange:t}){let[n,i]=r.useState(e),a=r.useRef(n),o=r.useRef(t);return u(()=>{o.current=t},[t]),r.useEffect(()=>{a.current!==n&&(o.current?.(n),a.current=n)},[n,a]),[n,i,o]}function p(e){return typeof e==`function`}export{o as a,a as i,l as n,c as r,d as t};
|
||||
@@ -1,4 +1,4 @@
|
||||
import{r as e}from"./chunk-DECur_0Z.js";import{t}from"./react-CO2uhaBc.js";import{Zp as n}from"./messages-BLwDcEAr.js";import{t as r}from"./dist-XiAcd8rT.js";import{s as i,u as a}from"./button-C0SPdqvs.js";import{a as o,i as s,r as c,t as l}from"./dist-D343v8gR.js";import{t as u}from"./dist-C9d8iJDv.js";import{n as d}from"./dist-BphrRiSn.js";import{n as f,t as p}from"./dist-Cd7-TPaR.js";import{i as m,n as h,r as g,t as _}from"./es2015-hwcIKy2q.js";var v=e(t(),1),y=n(),b=`Dialog`,[x,S]=o(b),[C,w]=x(b),T=e=>{let{__scopeDialog:t,children:n,open:r,defaultOpen:i,onOpenChange:a,modal:o=!0}=e,s=v.useRef(null),c=v.useRef(null),[u,f]=l({prop:r,defaultProp:i??!1,onChange:a,caller:b});return(0,y.jsx)(C,{scope:t,triggerRef:s,contentRef:c,contentId:d(),titleId:d(),descriptionId:d(),open:u,onOpenChange:f,onOpenToggle:v.useCallback(()=>f(e=>!e),[f]),modal:o,children:n})};T.displayName=b;var E=`DialogTrigger`,D=v.forwardRef((e,t)=>{let{__scopeDialog:n,...i}=e,o=w(E,n),s=a(t,o.triggerRef);return(0,y.jsx)(r.button,{type:`button`,"aria-haspopup":`dialog`,"aria-expanded":o.open,"aria-controls":o.contentId,"data-state":q(o.open),...i,ref:s,onClick:c(e.onClick,o.onOpenToggle)})});D.displayName=E;var O=`DialogPortal`,[k,A]=x(O,{forceMount:void 0}),j=e=>{let{__scopeDialog:t,forceMount:n,children:r,container:i}=e,a=w(O,t);return(0,y.jsx)(k,{scope:t,forceMount:n,children:v.Children.map(r,e=>(0,y.jsx)(u,{present:n||a.open,children:(0,y.jsx)(p,{asChild:!0,container:i,children:e})}))})};j.displayName=O;var M=`DialogOverlay`,N=v.forwardRef((e,t)=>{let n=A(M,e.__scopeDialog),{forceMount:r=n.forceMount,...i}=e,a=w(M,e.__scopeDialog);return a.modal?(0,y.jsx)(u,{present:r||a.open,children:(0,y.jsx)(F,{...i,ref:t})}):null});N.displayName=M;var P=i(`DialogOverlay.RemoveScroll`),F=v.forwardRef((e,t)=>{let{__scopeDialog:n,...i}=e,a=w(M,n);return(0,y.jsx)(h,{as:P,allowPinchZoom:!0,shards:[a.contentRef],children:(0,y.jsx)(r.div,{"data-state":q(a.open),...i,ref:t,style:{pointerEvents:`auto`,...i.style}})})}),I=`DialogContent`,L=v.forwardRef((e,t)=>{let n=A(I,e.__scopeDialog),{forceMount:r=n.forceMount,...i}=e,a=w(I,e.__scopeDialog);return(0,y.jsx)(u,{present:r||a.open,children:a.modal?(0,y.jsx)(R,{...i,ref:t}):(0,y.jsx)(z,{...i,ref:t})})});L.displayName=I;var R=v.forwardRef((e,t)=>{let n=w(I,e.__scopeDialog),r=v.useRef(null),i=a(t,n.contentRef,r);return v.useEffect(()=>{let e=r.current;if(e)return _(e)},[]),(0,y.jsx)(B,{...e,ref:i,trapFocus:n.open,disableOutsidePointerEvents:!0,onCloseAutoFocus:c(e.onCloseAutoFocus,e=>{e.preventDefault(),n.triggerRef.current?.focus()}),onPointerDownOutside:c(e.onPointerDownOutside,e=>{let t=e.detail.originalEvent,n=t.button===0&&t.ctrlKey===!0;(t.button===2||n)&&e.preventDefault()}),onFocusOutside:c(e.onFocusOutside,e=>e.preventDefault())})}),z=v.forwardRef((e,t)=>{let n=w(I,e.__scopeDialog),r=v.useRef(!1),i=v.useRef(!1);return(0,y.jsx)(B,{...e,ref:t,trapFocus:!1,disableOutsidePointerEvents:!1,onCloseAutoFocus:t=>{e.onCloseAutoFocus?.(t),t.defaultPrevented||(r.current||n.triggerRef.current?.focus(),t.preventDefault()),r.current=!1,i.current=!1},onInteractOutside:t=>{e.onInteractOutside?.(t),t.defaultPrevented||(r.current=!0,t.detail.originalEvent.type===`pointerdown`&&(i.current=!0));let a=t.target;n.triggerRef.current?.contains(a)&&t.preventDefault(),t.detail.originalEvent.type===`focusin`&&i.current&&t.preventDefault()}})}),B=v.forwardRef((e,t)=>{let{__scopeDialog:n,trapFocus:r,onOpenAutoFocus:i,onCloseAutoFocus:o,...s}=e,c=w(I,n),l=v.useRef(null),u=a(t,l);return g(),(0,y.jsxs)(y.Fragment,{children:[(0,y.jsx)(m,{asChild:!0,loop:!0,trapped:r,onMountAutoFocus:i,onUnmountAutoFocus:o,children:(0,y.jsx)(f,{role:`dialog`,id:c.contentId,"aria-describedby":c.descriptionId,"aria-labelledby":c.titleId,"data-state":q(c.open),...s,ref:u,onDismiss:()=>c.onOpenChange(!1)})}),(0,y.jsxs)(y.Fragment,{children:[(0,y.jsx)(Z,{titleId:c.titleId}),(0,y.jsx)($,{contentRef:l,descriptionId:c.descriptionId})]})]})}),V=`DialogTitle`,H=v.forwardRef((e,t)=>{let{__scopeDialog:n,...i}=e,a=w(V,n);return(0,y.jsx)(r.h2,{id:a.titleId,...i,ref:t})});H.displayName=V;var U=`DialogDescription`,W=v.forwardRef((e,t)=>{let{__scopeDialog:n,...i}=e,a=w(U,n);return(0,y.jsx)(r.p,{id:a.descriptionId,...i,ref:t})});W.displayName=U;var G=`DialogClose`,K=v.forwardRef((e,t)=>{let{__scopeDialog:n,...i}=e,a=w(G,n);return(0,y.jsx)(r.button,{type:`button`,...i,ref:t,onClick:c(e.onClick,()=>a.onOpenChange(!1))})});K.displayName=G;function q(e){return e?`open`:`closed`}var J=`DialogTitleWarning`,[Y,X]=s(J,{contentName:I,titleName:V,docsSlug:`dialog`}),Z=({titleId:e})=>{let t=X(J),n=`\`${t.contentName}\` requires a \`${t.titleName}\` for the component to be accessible for screen reader users.
|
||||
import{r as e}from"./chunk-DECur_0Z.js";import{t}from"./react-CO2uhaBc.js";import{em as n}from"./messages-Bp0bCjzp.js";import{t as r}from"./dist-BFnxq45V.js";import{s as i,u as a}from"./button-BgMnOYKD.js";import{a as o,i as s,r as c,t as l}from"./dist-CKFLmh1A.js";import{t as u}from"./dist-Clua1vrx.js";import{n as d}from"./dist-B0ikDpJU.js";import{n as f,t as p}from"./dist-_ND6L-P3.js";import{i as m,n as h,r as g,t as _}from"./es2015-D8VLlhse.js";var v=e(t(),1),y=n(),b=`Dialog`,[x,S]=o(b),[C,w]=x(b),T=e=>{let{__scopeDialog:t,children:n,open:r,defaultOpen:i,onOpenChange:a,modal:o=!0}=e,s=v.useRef(null),c=v.useRef(null),[u,f]=l({prop:r,defaultProp:i??!1,onChange:a,caller:b});return(0,y.jsx)(C,{scope:t,triggerRef:s,contentRef:c,contentId:d(),titleId:d(),descriptionId:d(),open:u,onOpenChange:f,onOpenToggle:v.useCallback(()=>f(e=>!e),[f]),modal:o,children:n})};T.displayName=b;var E=`DialogTrigger`,D=v.forwardRef((e,t)=>{let{__scopeDialog:n,...i}=e,o=w(E,n),s=a(t,o.triggerRef);return(0,y.jsx)(r.button,{type:`button`,"aria-haspopup":`dialog`,"aria-expanded":o.open,"aria-controls":o.contentId,"data-state":q(o.open),...i,ref:s,onClick:c(e.onClick,o.onOpenToggle)})});D.displayName=E;var O=`DialogPortal`,[k,A]=x(O,{forceMount:void 0}),j=e=>{let{__scopeDialog:t,forceMount:n,children:r,container:i}=e,a=w(O,t);return(0,y.jsx)(k,{scope:t,forceMount:n,children:v.Children.map(r,e=>(0,y.jsx)(u,{present:n||a.open,children:(0,y.jsx)(p,{asChild:!0,container:i,children:e})}))})};j.displayName=O;var M=`DialogOverlay`,N=v.forwardRef((e,t)=>{let n=A(M,e.__scopeDialog),{forceMount:r=n.forceMount,...i}=e,a=w(M,e.__scopeDialog);return a.modal?(0,y.jsx)(u,{present:r||a.open,children:(0,y.jsx)(F,{...i,ref:t})}):null});N.displayName=M;var P=i(`DialogOverlay.RemoveScroll`),F=v.forwardRef((e,t)=>{let{__scopeDialog:n,...i}=e,a=w(M,n);return(0,y.jsx)(h,{as:P,allowPinchZoom:!0,shards:[a.contentRef],children:(0,y.jsx)(r.div,{"data-state":q(a.open),...i,ref:t,style:{pointerEvents:`auto`,...i.style}})})}),I=`DialogContent`,L=v.forwardRef((e,t)=>{let n=A(I,e.__scopeDialog),{forceMount:r=n.forceMount,...i}=e,a=w(I,e.__scopeDialog);return(0,y.jsx)(u,{present:r||a.open,children:a.modal?(0,y.jsx)(R,{...i,ref:t}):(0,y.jsx)(z,{...i,ref:t})})});L.displayName=I;var R=v.forwardRef((e,t)=>{let n=w(I,e.__scopeDialog),r=v.useRef(null),i=a(t,n.contentRef,r);return v.useEffect(()=>{let e=r.current;if(e)return _(e)},[]),(0,y.jsx)(B,{...e,ref:i,trapFocus:n.open,disableOutsidePointerEvents:!0,onCloseAutoFocus:c(e.onCloseAutoFocus,e=>{e.preventDefault(),n.triggerRef.current?.focus()}),onPointerDownOutside:c(e.onPointerDownOutside,e=>{let t=e.detail.originalEvent,n=t.button===0&&t.ctrlKey===!0;(t.button===2||n)&&e.preventDefault()}),onFocusOutside:c(e.onFocusOutside,e=>e.preventDefault())})}),z=v.forwardRef((e,t)=>{let n=w(I,e.__scopeDialog),r=v.useRef(!1),i=v.useRef(!1);return(0,y.jsx)(B,{...e,ref:t,trapFocus:!1,disableOutsidePointerEvents:!1,onCloseAutoFocus:t=>{e.onCloseAutoFocus?.(t),t.defaultPrevented||(r.current||n.triggerRef.current?.focus(),t.preventDefault()),r.current=!1,i.current=!1},onInteractOutside:t=>{e.onInteractOutside?.(t),t.defaultPrevented||(r.current=!0,t.detail.originalEvent.type===`pointerdown`&&(i.current=!0));let a=t.target;n.triggerRef.current?.contains(a)&&t.preventDefault(),t.detail.originalEvent.type===`focusin`&&i.current&&t.preventDefault()}})}),B=v.forwardRef((e,t)=>{let{__scopeDialog:n,trapFocus:r,onOpenAutoFocus:i,onCloseAutoFocus:o,...s}=e,c=w(I,n),l=v.useRef(null),u=a(t,l);return g(),(0,y.jsxs)(y.Fragment,{children:[(0,y.jsx)(m,{asChild:!0,loop:!0,trapped:r,onMountAutoFocus:i,onUnmountAutoFocus:o,children:(0,y.jsx)(f,{role:`dialog`,id:c.contentId,"aria-describedby":c.descriptionId,"aria-labelledby":c.titleId,"data-state":q(c.open),...s,ref:u,onDismiss:()=>c.onOpenChange(!1)})}),(0,y.jsxs)(y.Fragment,{children:[(0,y.jsx)(Z,{titleId:c.titleId}),(0,y.jsx)($,{contentRef:l,descriptionId:c.descriptionId})]})]})}),V=`DialogTitle`,H=v.forwardRef((e,t)=>{let{__scopeDialog:n,...i}=e,a=w(V,n);return(0,y.jsx)(r.h2,{id:a.titleId,...i,ref:t})});H.displayName=V;var U=`DialogDescription`,W=v.forwardRef((e,t)=>{let{__scopeDialog:n,...i}=e,a=w(U,n);return(0,y.jsx)(r.p,{id:a.descriptionId,...i,ref:t})});W.displayName=U;var G=`DialogClose`,K=v.forwardRef((e,t)=>{let{__scopeDialog:n,...i}=e,a=w(G,n);return(0,y.jsx)(r.button,{type:`button`,...i,ref:t,onClick:c(e.onClick,()=>a.onOpenChange(!1))})});K.displayName=G;function q(e){return e?`open`:`closed`}var J=`DialogTitleWarning`,[Y,X]=s(J,{contentName:I,titleName:V,docsSlug:`dialog`}),Z=({titleId:e})=>{let t=X(J),n=`\`${t.contentName}\` requires a \`${t.titleName}\` for the component to be accessible for screen reader users.
|
||||
|
||||
If you want to hide the \`${t.titleName}\`, you can wrap it with our VisuallyHidden component.
|
||||
|
||||
@@ -1 +1 @@
|
||||
import{r as e}from"./chunk-DECur_0Z.js";import{t}from"./react-CO2uhaBc.js";import{u as n}from"./button-C0SPdqvs.js";import{n as r}from"./dist-D343v8gR.js";var i=e(t(),1);function a(e,t){return i.useReducer((e,n)=>t[e][n]??e,e)}var o=e=>{let{present:t,children:r}=e,a=s(t),o=typeof r==`function`?r({present:a.isPresent}):i.Children.only(r),c=n(a.ref,l(o));return typeof r==`function`||a.isPresent?i.cloneElement(o,{ref:c}):null};o.displayName=`Presence`;function s(e){let[t,n]=i.useState(),o=i.useRef(null),s=i.useRef(e),l=i.useRef(`none`),[u,d]=a(e?`mounted`:`unmounted`,{mounted:{UNMOUNT:`unmounted`,ANIMATION_OUT:`unmountSuspended`},unmountSuspended:{MOUNT:`mounted`,ANIMATION_END:`unmounted`},unmounted:{MOUNT:`mounted`}});return i.useEffect(()=>{let e=c(o.current);l.current=u===`mounted`?e:`none`},[u]),r(()=>{let t=o.current,n=s.current;if(n!==e){let r=l.current,i=c(t);e?d(`MOUNT`):i===`none`||t?.display===`none`?d(`UNMOUNT`):d(n&&r!==i?`ANIMATION_OUT`:`UNMOUNT`),s.current=e}},[e,d]),r(()=>{if(t){let e,n=t.ownerDocument.defaultView??window,r=r=>{let i=c(o.current).includes(CSS.escape(r.animationName));if(r.target===t&&i&&(d(`ANIMATION_END`),!s.current)){let r=t.style.animationFillMode;t.style.animationFillMode=`forwards`,e=n.setTimeout(()=>{t.style.animationFillMode===`forwards`&&(t.style.animationFillMode=r)})}},i=e=>{e.target===t&&(l.current=c(o.current))};return t.addEventListener(`animationstart`,i),t.addEventListener(`animationcancel`,r),t.addEventListener(`animationend`,r),()=>{n.clearTimeout(e),t.removeEventListener(`animationstart`,i),t.removeEventListener(`animationcancel`,r),t.removeEventListener(`animationend`,r)}}else d(`ANIMATION_END`)},[t,d]),{isPresent:[`mounted`,`unmountSuspended`].includes(u),ref:i.useCallback(e=>{o.current=e?getComputedStyle(e):null,n(e)},[])}}function c(e){return e?.animationName||`none`}function l(e){let t=Object.getOwnPropertyDescriptor(e.props,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}export{o as t};
|
||||
import{r as e}from"./chunk-DECur_0Z.js";import{t}from"./react-CO2uhaBc.js";import{u as n}from"./button-BgMnOYKD.js";import{n as r}from"./dist-CKFLmh1A.js";var i=e(t(),1);function a(e,t){return i.useReducer((e,n)=>t[e][n]??e,e)}var o=e=>{let{present:t,children:r}=e,a=s(t),o=typeof r==`function`?r({present:a.isPresent}):i.Children.only(r),c=n(a.ref,l(o));return typeof r==`function`||a.isPresent?i.cloneElement(o,{ref:c}):null};o.displayName=`Presence`;function s(e){let[t,n]=i.useState(),o=i.useRef(null),s=i.useRef(e),l=i.useRef(`none`),[u,d]=a(e?`mounted`:`unmounted`,{mounted:{UNMOUNT:`unmounted`,ANIMATION_OUT:`unmountSuspended`},unmountSuspended:{MOUNT:`mounted`,ANIMATION_END:`unmounted`},unmounted:{MOUNT:`mounted`}});return i.useEffect(()=>{let e=c(o.current);l.current=u===`mounted`?e:`none`},[u]),r(()=>{let t=o.current,n=s.current;if(n!==e){let r=l.current,i=c(t);e?d(`MOUNT`):i===`none`||t?.display===`none`?d(`UNMOUNT`):d(n&&r!==i?`ANIMATION_OUT`:`UNMOUNT`),s.current=e}},[e,d]),r(()=>{if(t){let e,n=t.ownerDocument.defaultView??window,r=r=>{let i=c(o.current).includes(CSS.escape(r.animationName));if(r.target===t&&i&&(d(`ANIMATION_END`),!s.current)){let r=t.style.animationFillMode;t.style.animationFillMode=`forwards`,e=n.setTimeout(()=>{t.style.animationFillMode===`forwards`&&(t.style.animationFillMode=r)})}},i=e=>{e.target===t&&(l.current=c(o.current))};return t.addEventListener(`animationstart`,i),t.addEventListener(`animationcancel`,r),t.addEventListener(`animationend`,r),()=>{n.clearTimeout(e),t.removeEventListener(`animationstart`,i),t.removeEventListener(`animationcancel`,r),t.removeEventListener(`animationend`,r)}}else d(`ANIMATION_END`)},[t,d]),{isPresent:[`mounted`,`unmountSuspended`].includes(u),ref:i.useCallback(e=>{o.current=e?getComputedStyle(e):null,n(e)},[])}}function c(e){return e?.animationName||`none`}function l(e){let t=Object.getOwnPropertyDescriptor(e.props,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,`ref`)?.get,n=t&&`isReactWarning`in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}export{o as t};
|
||||
@@ -1 +1 @@
|
||||
import{r as e}from"./chunk-DECur_0Z.js";import{t}from"./react-CO2uhaBc.js";import{Zp as n}from"./messages-BLwDcEAr.js";import{t as r}from"./dist-XiAcd8rT.js";import{u as i}from"./button-C0SPdqvs.js";import{a,r as o,t as s}from"./dist-D343v8gR.js";import{t as c}from"./dist-THZWKIyP.js";import{n as l,t as u}from"./dist-BphrRiSn.js";import{n as d}from"./dist-CYQVWHzW.js";var f=e(t(),1),p=n(),m=`rovingFocusGroup.onEntryFocus`,h={bubbles:!1,cancelable:!0},g=`RovingFocusGroup`,[_,v,y]=c(g),[b,x]=a(g,[y]),[S,C]=b(g),w=f.forwardRef((e,t)=>(0,p.jsx)(_.Provider,{scope:e.__scopeRovingFocusGroup,children:(0,p.jsx)(_.Slot,{scope:e.__scopeRovingFocusGroup,children:(0,p.jsx)(T,{...e,ref:t})})}));w.displayName=g;var T=f.forwardRef((e,t)=>{let{__scopeRovingFocusGroup:n,orientation:a,loop:c=!1,dir:l,currentTabStopId:_,defaultCurrentTabStopId:y,onCurrentTabStopIdChange:b,onEntryFocus:x,preventScrollOnEntryFocus:C=!1,...w}=e,T=f.useRef(null),E=i(t,T),D=d(l),[O,k]=s({prop:_,defaultProp:y??null,onChange:b,caller:g}),[A,M]=f.useState(!1),N=u(x),P=v(n),F=f.useRef(!1),[I,L]=f.useState(0);return f.useEffect(()=>{let e=T.current;if(e)return e.addEventListener(m,N),()=>e.removeEventListener(m,N)},[N]),(0,p.jsx)(S,{scope:n,orientation:a,dir:D,loop:c,currentTabStopId:O,onItemFocus:f.useCallback(e=>k(e),[k]),onItemShiftTab:f.useCallback(()=>M(!0),[]),onFocusableItemAdd:f.useCallback(()=>L(e=>e+1),[]),onFocusableItemRemove:f.useCallback(()=>L(e=>e-1),[]),children:(0,p.jsx)(r.div,{tabIndex:A||I===0?-1:0,"data-orientation":a,...w,ref:E,style:{outline:`none`,...e.style},onMouseDown:o(e.onMouseDown,()=>{F.current=!0}),onFocus:o(e.onFocus,e=>{let t=!F.current;if(e.target===e.currentTarget&&t&&!A){let t=new CustomEvent(m,h);if(e.currentTarget.dispatchEvent(t),!t.defaultPrevented){let e=P().filter(e=>e.focusable);j([e.find(e=>e.active),e.find(e=>e.id===O),...e].filter(Boolean).map(e=>e.ref.current),C)}}F.current=!1}),onBlur:o(e.onBlur,()=>M(!1))})})}),E=`RovingFocusGroupItem`,D=f.forwardRef((e,t)=>{let{__scopeRovingFocusGroup:n,focusable:i=!0,active:a=!1,tabStopId:s,children:c,...u}=e,d=l(),m=s||d,h=C(E,n),g=h.currentTabStopId===m,y=v(n),{onFocusableItemAdd:b,onFocusableItemRemove:x,currentTabStopId:S}=h;return f.useEffect(()=>{if(i)return b(),()=>x()},[i,b,x]),(0,p.jsx)(_.ItemSlot,{scope:n,id:m,focusable:i,active:a,children:(0,p.jsx)(r.span,{tabIndex:g?0:-1,"data-orientation":h.orientation,...u,ref:t,onMouseDown:o(e.onMouseDown,e=>{i?h.onItemFocus(m):e.preventDefault()}),onFocus:o(e.onFocus,()=>h.onItemFocus(m)),onKeyDown:o(e.onKeyDown,e=>{if(e.key===`Tab`&&e.shiftKey){h.onItemShiftTab();return}if(e.target!==e.currentTarget)return;let t=A(e,h.orientation,h.dir);if(t!==void 0){if(e.metaKey||e.ctrlKey||e.altKey||e.shiftKey)return;e.preventDefault();let n=y().filter(e=>e.focusable).map(e=>e.ref.current);if(t===`last`)n.reverse();else if(t===`prev`||t===`next`){t===`prev`&&n.reverse();let r=n.indexOf(e.currentTarget);n=h.loop?M(n,r+1):n.slice(r+1)}setTimeout(()=>j(n))}}),children:typeof c==`function`?c({isCurrentTabStop:g,hasTabStop:S!=null}):c})})});D.displayName=E;var O={ArrowLeft:`prev`,ArrowUp:`prev`,ArrowRight:`next`,ArrowDown:`next`,PageUp:`first`,Home:`first`,PageDown:`last`,End:`last`};function k(e,t){return t===`rtl`?e===`ArrowLeft`?`ArrowRight`:e===`ArrowRight`?`ArrowLeft`:e:e}function A(e,t,n){let r=k(e.key,n);if(!(t===`vertical`&&[`ArrowLeft`,`ArrowRight`].includes(r))&&!(t===`horizontal`&&[`ArrowUp`,`ArrowDown`].includes(r)))return O[r]}function j(e,t=!1){let n=document.activeElement;for(let r of e)if(r===n||(r.focus({preventScroll:t}),document.activeElement!==n))return}function M(e,t){return e.map((n,r)=>e[(t+r)%e.length])}var N=w,P=D;export{N as n,x as r,P as t};
|
||||
import{r as e}from"./chunk-DECur_0Z.js";import{t}from"./react-CO2uhaBc.js";import{em as n}from"./messages-Bp0bCjzp.js";import{t as r}from"./dist-BFnxq45V.js";import{u as i}from"./button-BgMnOYKD.js";import{a,r as o,t as s}from"./dist-CKFLmh1A.js";import{t as c}from"./dist-C6i4A_Js.js";import{n as l,t as u}from"./dist-B0ikDpJU.js";import{n as d}from"./dist-C97YBjnT.js";var f=e(t(),1),p=n(),m=`rovingFocusGroup.onEntryFocus`,h={bubbles:!1,cancelable:!0},g=`RovingFocusGroup`,[_,v,y]=c(g),[b,x]=a(g,[y]),[S,C]=b(g),w=f.forwardRef((e,t)=>(0,p.jsx)(_.Provider,{scope:e.__scopeRovingFocusGroup,children:(0,p.jsx)(_.Slot,{scope:e.__scopeRovingFocusGroup,children:(0,p.jsx)(T,{...e,ref:t})})}));w.displayName=g;var T=f.forwardRef((e,t)=>{let{__scopeRovingFocusGroup:n,orientation:a,loop:c=!1,dir:l,currentTabStopId:_,defaultCurrentTabStopId:y,onCurrentTabStopIdChange:b,onEntryFocus:x,preventScrollOnEntryFocus:C=!1,...w}=e,T=f.useRef(null),E=i(t,T),D=d(l),[O,k]=s({prop:_,defaultProp:y??null,onChange:b,caller:g}),[A,M]=f.useState(!1),N=u(x),P=v(n),F=f.useRef(!1),[I,L]=f.useState(0);return f.useEffect(()=>{let e=T.current;if(e)return e.addEventListener(m,N),()=>e.removeEventListener(m,N)},[N]),(0,p.jsx)(S,{scope:n,orientation:a,dir:D,loop:c,currentTabStopId:O,onItemFocus:f.useCallback(e=>k(e),[k]),onItemShiftTab:f.useCallback(()=>M(!0),[]),onFocusableItemAdd:f.useCallback(()=>L(e=>e+1),[]),onFocusableItemRemove:f.useCallback(()=>L(e=>e-1),[]),children:(0,p.jsx)(r.div,{tabIndex:A||I===0?-1:0,"data-orientation":a,...w,ref:E,style:{outline:`none`,...e.style},onMouseDown:o(e.onMouseDown,()=>{F.current=!0}),onFocus:o(e.onFocus,e=>{let t=!F.current;if(e.target===e.currentTarget&&t&&!A){let t=new CustomEvent(m,h);if(e.currentTarget.dispatchEvent(t),!t.defaultPrevented){let e=P().filter(e=>e.focusable);j([e.find(e=>e.active),e.find(e=>e.id===O),...e].filter(Boolean).map(e=>e.ref.current),C)}}F.current=!1}),onBlur:o(e.onBlur,()=>M(!1))})})}),E=`RovingFocusGroupItem`,D=f.forwardRef((e,t)=>{let{__scopeRovingFocusGroup:n,focusable:i=!0,active:a=!1,tabStopId:s,children:c,...u}=e,d=l(),m=s||d,h=C(E,n),g=h.currentTabStopId===m,y=v(n),{onFocusableItemAdd:b,onFocusableItemRemove:x,currentTabStopId:S}=h;return f.useEffect(()=>{if(i)return b(),()=>x()},[i,b,x]),(0,p.jsx)(_.ItemSlot,{scope:n,id:m,focusable:i,active:a,children:(0,p.jsx)(r.span,{tabIndex:g?0:-1,"data-orientation":h.orientation,...u,ref:t,onMouseDown:o(e.onMouseDown,e=>{i?h.onItemFocus(m):e.preventDefault()}),onFocus:o(e.onFocus,()=>h.onItemFocus(m)),onKeyDown:o(e.onKeyDown,e=>{if(e.key===`Tab`&&e.shiftKey){h.onItemShiftTab();return}if(e.target!==e.currentTarget)return;let t=A(e,h.orientation,h.dir);if(t!==void 0){if(e.metaKey||e.ctrlKey||e.altKey||e.shiftKey)return;e.preventDefault();let n=y().filter(e=>e.focusable).map(e=>e.ref.current);if(t===`last`)n.reverse();else if(t===`prev`||t===`next`){t===`prev`&&n.reverse();let r=n.indexOf(e.currentTarget);n=h.loop?M(n,r+1):n.slice(r+1)}setTimeout(()=>j(n))}}),children:typeof c==`function`?c({isCurrentTabStop:g,hasTabStop:S!=null}):c})})});D.displayName=E;var O={ArrowLeft:`prev`,ArrowUp:`prev`,ArrowRight:`next`,ArrowDown:`next`,PageUp:`first`,Home:`first`,PageDown:`last`,End:`last`};function k(e,t){return t===`rtl`?e===`ArrowLeft`?`ArrowRight`:e===`ArrowRight`?`ArrowLeft`:e:e}function A(e,t,n){let r=k(e.key,n);if(!(t===`vertical`&&[`ArrowLeft`,`ArrowRight`].includes(r))&&!(t===`horizontal`&&[`ArrowUp`,`ArrowDown`].includes(r)))return O[r]}function j(e,t=!1){let n=document.activeElement;for(let r of e)if(r===n||(r.focus({preventScroll:t}),document.activeElement!==n))return}function M(e,t){return e.map((n,r)=>e[(t+r)%e.length])}var N=w,P=D;export{N as n,x as r,P as t};
|
||||
@@ -1 +1 @@
|
||||
import{r as e}from"./chunk-DECur_0Z.js";import{t}from"./react-CO2uhaBc.js";import{n}from"./dist-D343v8gR.js";var r=e(t(),1);function i(e){let[t,i]=r.useState(void 0);return n(()=>{if(e){i({width:e.offsetWidth,height:e.offsetHeight});let t=new ResizeObserver(t=>{if(!Array.isArray(t)||!t.length)return;let n=t[0],r,a;if(`borderBoxSize`in n){let e=n.borderBoxSize,t=Array.isArray(e)?e[0]:e;r=t.inlineSize,a=t.blockSize}else r=e.offsetWidth,a=e.offsetHeight;i({width:r,height:a})});return t.observe(e,{box:`border-box`}),()=>t.unobserve(e)}else i(void 0)},[e]),t}export{i as t};
|
||||
import{r as e}from"./chunk-DECur_0Z.js";import{t}from"./react-CO2uhaBc.js";import{n}from"./dist-CKFLmh1A.js";var r=e(t(),1);function i(e){let[t,i]=r.useState(void 0);return n(()=>{if(e){i({width:e.offsetWidth,height:e.offsetHeight});let t=new ResizeObserver(t=>{if(!Array.isArray(t)||!t.length)return;let n=t[0],r,a;if(`borderBoxSize`in n){let e=n.borderBoxSize,t=Array.isArray(e)?e[0]:e;r=t.inlineSize,a=t.blockSize}else r=e.offsetWidth,a=e.offsetHeight;i({width:r,height:a})});return t.observe(e,{box:`border-box`}),()=>t.unobserve(e)}else i(void 0)},[e]),t}export{i as t};
|
||||
@@ -1 +1 @@
|
||||
import{r as e}from"./chunk-DECur_0Z.js";import{t}from"./react-CO2uhaBc.js";import{Zp as n}from"./messages-BLwDcEAr.js";import{n as r,r as i,t as a}from"./dist-XiAcd8rT.js";import{u as o}from"./button-C0SPdqvs.js";import{n as s,r as c}from"./dist-D343v8gR.js";import{t as l}from"./dist-BphrRiSn.js";var u=e(t(),1);function d(e,t=globalThis?.document){let n=l(e);u.useEffect(()=>{let e=e=>{e.key===`Escape`&&n(e)};return t.addEventListener(`keydown`,e,{capture:!0}),()=>t.removeEventListener(`keydown`,e,{capture:!0})},[n,t])}var f=n(),p=`DismissableLayer`,m=`dismissableLayer.update`,h=`dismissableLayer.pointerDownOutside`,g=`dismissableLayer.focusOutside`,_,v=u.createContext({layers:new Set,layersWithOutsidePointerEventsDisabled:new Set,branches:new Set}),y=u.forwardRef((e,t)=>{let{disableOutsidePointerEvents:n=!1,onEscapeKeyDown:r,onPointerDownOutside:i,onFocusOutside:s,onInteractOutside:l,onDismiss:p,...h}=e,g=u.useContext(v),[y,b]=u.useState(null),x=y?.ownerDocument??globalThis?.document,[,T]=u.useState({}),E=o(t,e=>b(e)),D=Array.from(g.layers),[O]=[...g.layersWithOutsidePointerEventsDisabled].slice(-1),k=D.indexOf(O),A=y?D.indexOf(y):-1,j=g.layersWithOutsidePointerEventsDisabled.size>0,M=A>=k,N=S(e=>{let t=e.target,n=[...g.branches].some(e=>e.contains(t));!M||n||(i?.(e),l?.(e),e.defaultPrevented||p?.())},x),P=C(e=>{let t=e.target;[...g.branches].some(e=>e.contains(t))||(s?.(e),l?.(e),e.defaultPrevented||p?.())},x);return d(e=>{A===g.layers.size-1&&(r?.(e),!e.defaultPrevented&&p&&(e.preventDefault(),p()))},x),u.useEffect(()=>{if(y)return n&&(g.layersWithOutsidePointerEventsDisabled.size===0&&(_=x.body.style.pointerEvents,x.body.style.pointerEvents=`none`),g.layersWithOutsidePointerEventsDisabled.add(y)),g.layers.add(y),w(),()=>{n&&g.layersWithOutsidePointerEventsDisabled.size===1&&(x.body.style.pointerEvents=_)}},[y,x,n,g]),u.useEffect(()=>()=>{y&&(g.layers.delete(y),g.layersWithOutsidePointerEventsDisabled.delete(y),w())},[y,g]),u.useEffect(()=>{let e=()=>T({});return document.addEventListener(m,e),()=>document.removeEventListener(m,e)},[]),(0,f.jsx)(a.div,{...h,ref:E,style:{pointerEvents:j?M?`auto`:`none`:void 0,...e.style},onFocusCapture:c(e.onFocusCapture,P.onFocusCapture),onBlurCapture:c(e.onBlurCapture,P.onBlurCapture),onPointerDownCapture:c(e.onPointerDownCapture,N.onPointerDownCapture)})});y.displayName=p;var b=`DismissableLayerBranch`,x=u.forwardRef((e,t)=>{let n=u.useContext(v),r=u.useRef(null),i=o(t,r);return u.useEffect(()=>{let e=r.current;if(e)return n.branches.add(e),()=>{n.branches.delete(e)}},[n.branches]),(0,f.jsx)(a.div,{...e,ref:i})});x.displayName=b;function S(e,t=globalThis?.document){let n=l(e),r=u.useRef(!1),i=u.useRef(()=>{});return u.useEffect(()=>{let e=e=>{if(e.target&&!r.current){let r=function(){T(h,n,a,{discrete:!0})},a={originalEvent:e};e.pointerType===`touch`?(t.removeEventListener(`click`,i.current),i.current=r,t.addEventListener(`click`,i.current,{once:!0})):r()}else t.removeEventListener(`click`,i.current);r.current=!1},a=window.setTimeout(()=>{t.addEventListener(`pointerdown`,e)},0);return()=>{window.clearTimeout(a),t.removeEventListener(`pointerdown`,e),t.removeEventListener(`click`,i.current)}},[t,n]),{onPointerDownCapture:()=>r.current=!0}}function C(e,t=globalThis?.document){let n=l(e),r=u.useRef(!1);return u.useEffect(()=>{let e=e=>{e.target&&!r.current&&T(g,n,{originalEvent:e},{discrete:!1})};return t.addEventListener(`focusin`,e),()=>t.removeEventListener(`focusin`,e)},[t,n]),{onFocusCapture:()=>r.current=!0,onBlurCapture:()=>r.current=!1}}function w(){let e=new CustomEvent(m);document.dispatchEvent(e)}function T(e,t,n,{discrete:i}){let a=n.originalEvent.target,o=new CustomEvent(e,{bubbles:!1,cancelable:!0,detail:n});t&&a.addEventListener(e,t,{once:!0}),i?r(a,o):a.dispatchEvent(o)}var E=e(i(),1),D=`Portal`,O=u.forwardRef((e,t)=>{let{container:n,...r}=e,[i,o]=u.useState(!1);s(()=>o(!0),[]);let c=n||i&&globalThis?.document?.body;return c?E.createPortal((0,f.jsx)(a.div,{...r,ref:t}),c):null});O.displayName=D;export{y as n,O as t};
|
||||
import{r as e}from"./chunk-DECur_0Z.js";import{t}from"./react-CO2uhaBc.js";import{em as n}from"./messages-Bp0bCjzp.js";import{n as r,r as i,t as a}from"./dist-BFnxq45V.js";import{u as o}from"./button-BgMnOYKD.js";import{n as s,r as c}from"./dist-CKFLmh1A.js";import{t as l}from"./dist-B0ikDpJU.js";var u=e(t(),1);function d(e,t=globalThis?.document){let n=l(e);u.useEffect(()=>{let e=e=>{e.key===`Escape`&&n(e)};return t.addEventListener(`keydown`,e,{capture:!0}),()=>t.removeEventListener(`keydown`,e,{capture:!0})},[n,t])}var f=n(),p=`DismissableLayer`,m=`dismissableLayer.update`,h=`dismissableLayer.pointerDownOutside`,g=`dismissableLayer.focusOutside`,_,v=u.createContext({layers:new Set,layersWithOutsidePointerEventsDisabled:new Set,branches:new Set}),y=u.forwardRef((e,t)=>{let{disableOutsidePointerEvents:n=!1,onEscapeKeyDown:r,onPointerDownOutside:i,onFocusOutside:s,onInteractOutside:l,onDismiss:p,...h}=e,g=u.useContext(v),[y,b]=u.useState(null),x=y?.ownerDocument??globalThis?.document,[,T]=u.useState({}),E=o(t,e=>b(e)),D=Array.from(g.layers),[O]=[...g.layersWithOutsidePointerEventsDisabled].slice(-1),k=D.indexOf(O),A=y?D.indexOf(y):-1,j=g.layersWithOutsidePointerEventsDisabled.size>0,M=A>=k,N=S(e=>{let t=e.target,n=[...g.branches].some(e=>e.contains(t));!M||n||(i?.(e),l?.(e),e.defaultPrevented||p?.())},x),P=C(e=>{let t=e.target;[...g.branches].some(e=>e.contains(t))||(s?.(e),l?.(e),e.defaultPrevented||p?.())},x);return d(e=>{A===g.layers.size-1&&(r?.(e),!e.defaultPrevented&&p&&(e.preventDefault(),p()))},x),u.useEffect(()=>{if(y)return n&&(g.layersWithOutsidePointerEventsDisabled.size===0&&(_=x.body.style.pointerEvents,x.body.style.pointerEvents=`none`),g.layersWithOutsidePointerEventsDisabled.add(y)),g.layers.add(y),w(),()=>{n&&g.layersWithOutsidePointerEventsDisabled.size===1&&(x.body.style.pointerEvents=_)}},[y,x,n,g]),u.useEffect(()=>()=>{y&&(g.layers.delete(y),g.layersWithOutsidePointerEventsDisabled.delete(y),w())},[y,g]),u.useEffect(()=>{let e=()=>T({});return document.addEventListener(m,e),()=>document.removeEventListener(m,e)},[]),(0,f.jsx)(a.div,{...h,ref:E,style:{pointerEvents:j?M?`auto`:`none`:void 0,...e.style},onFocusCapture:c(e.onFocusCapture,P.onFocusCapture),onBlurCapture:c(e.onBlurCapture,P.onBlurCapture),onPointerDownCapture:c(e.onPointerDownCapture,N.onPointerDownCapture)})});y.displayName=p;var b=`DismissableLayerBranch`,x=u.forwardRef((e,t)=>{let n=u.useContext(v),r=u.useRef(null),i=o(t,r);return u.useEffect(()=>{let e=r.current;if(e)return n.branches.add(e),()=>{n.branches.delete(e)}},[n.branches]),(0,f.jsx)(a.div,{...e,ref:i})});x.displayName=b;function S(e,t=globalThis?.document){let n=l(e),r=u.useRef(!1),i=u.useRef(()=>{});return u.useEffect(()=>{let e=e=>{if(e.target&&!r.current){let r=function(){T(h,n,a,{discrete:!0})},a={originalEvent:e};e.pointerType===`touch`?(t.removeEventListener(`click`,i.current),i.current=r,t.addEventListener(`click`,i.current,{once:!0})):r()}else t.removeEventListener(`click`,i.current);r.current=!1},a=window.setTimeout(()=>{t.addEventListener(`pointerdown`,e)},0);return()=>{window.clearTimeout(a),t.removeEventListener(`pointerdown`,e),t.removeEventListener(`click`,i.current)}},[t,n]),{onPointerDownCapture:()=>r.current=!0}}function C(e,t=globalThis?.document){let n=l(e),r=u.useRef(!1);return u.useEffect(()=>{let e=e=>{e.target&&!r.current&&T(g,n,{originalEvent:e},{discrete:!1})};return t.addEventListener(`focusin`,e),()=>t.removeEventListener(`focusin`,e)},[t,n]),{onFocusCapture:()=>r.current=!0,onBlurCapture:()=>r.current=!1}}function w(){let e=new CustomEvent(m);document.dispatchEvent(e)}function T(e,t,n,{discrete:i}){let a=n.originalEvent.target,o=new CustomEvent(e,{bubbles:!1,cancelable:!0,detail:n});t&&a.addEventListener(e,t,{once:!0}),i?r(a,o):a.dispatchEvent(o)}var E=e(i(),1),D=`Portal`,O=u.forwardRef((e,t)=>{let{container:n,...r}=e,[i,o]=u.useState(!1);s(()=>o(!0),[]);let c=n||i&&globalThis?.document?.body;return c?E.createPortal((0,f.jsx)(a.div,{...r,ref:t}),c):null});O.displayName=D;export{y as n,O as t};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user