Commit b05f894e by haojie

1

parent 73b3f4c4
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Str;
use RuntimeException; use RuntimeException;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
namespace App\Exceptions; namespace App\Exceptions;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable; use Throwable;
...@@ -37,6 +38,27 @@ class Handler extends ExceptionHandler ...@@ -37,6 +38,27 @@ class Handler extends ExceptionHandler
]; ];
/** /**
* Render an exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
* @param \Exception $exception
* @return \Illuminate\Http\Response
*/
public function render($request, Throwable $exception)
{
if ($exception instanceof AppRuntimeException) {
return $exception::render($request, $exception);
}
if ($exception instanceof \Illuminate\Auth\AuthenticationException) {
$exception = new \App\Exceptions\UserException(0, 'TOKEN失效了', [], 403);
return AppRuntimeException::render($request, $exception);
}
return parent::render($request, $exception);
}
/**
* Register the exception handling callbacks for the application. * Register the exception handling callbacks for the application.
* *
* @return void * @return void
...@@ -47,4 +69,18 @@ public function register() ...@@ -47,4 +69,18 @@ public function register()
// //
}); });
} }
/**
* Convert an authentication exception into a response.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Auth\AuthenticationException $exception
* @return \Symfony\Component\HttpFoundation\Response
*/
protected function unauthenticated($request, AuthenticationException $exception)
{
return $request->expectsJson()
? response()->json(['message' => $exception->getMessage()], 401)
: redirect()->guest($exception->redirectTo() ?? route('login'));
}
} }
...@@ -14,7 +14,7 @@ public function wallet(Request $request) ...@@ -14,7 +14,7 @@ public function wallet(Request $request)
{ {
$address = $request->input('address'); $address = $request->input('address');
$result = app(InscriptionService::class)->getWalletInscription($address); $result = app(InscriptionService::class)->getWalletInscription($address);
return $this->success($result); return $this->success('success', $result);
} }
// 获取外部网站的市场铭文列表 // 获取外部网站的市场铭文列表
...@@ -23,7 +23,7 @@ public function outMarket(Request $request) ...@@ -23,7 +23,7 @@ public function outMarket(Request $request)
$tick = $request->input('tick'); $tick = $request->input('tick');
$type = $request->input('type'); $type = $request->input('type');
$result = app(InscriptionService::class)->getMarketInscription(); $result = app(InscriptionService::class)->getMarketInscription();
return $this->success($result); return $this->success('success', $result);
} }
} }
...@@ -21,7 +21,53 @@ public function shelves(Request $request) ...@@ -21,7 +21,53 @@ public function shelves(Request $request)
// 铭文信息 // 铭文信息
$inscription = $request->input('inscription'); $inscription = $request->input('inscription');
$result = app(TradeService::class)->userShelves($payment_address, $hash, $inscription, $price); $result = app(TradeService::class)->userShelves($payment_address, $hash, $inscription, $price);
return $this->success($result); return $this->success('success', $result);
}
// 交易检测回调
public function check(Request $request)
{
$data = $request->input();
$result = app(TradeService::class)->check($data);
return $this->success('success', $result);
}
// 铭文查询
public function search(Request $request)
{
$address = $request->input('address');
$type = $request->input('type');
$page = $request->input('page', 1);
$limit = $request->input('limit', 10);
// 限制最大
if ($limit > 50) {
$limit = 10;
}
$result = app(TradeService::class)->search($address, $type, $page, $limit);
return $this->success('success', $result);
}
// 获取平台交易中的铭文列表
public function inscriptionList(Request $request)
{
$page = $request->input('page', 1);
$limit = $request->input('limit', 10);
// 限制最大
if ($limit > 50) {
$limit = 10;
}
$result = app(TradeService::class)->inscriptionList($page, $limit);
return $this->success('success', $result);
}
// 买家支付
public function buy(Request $request)
{
$payment_address = $request->input('payment_address', '');
$hash = $request->input('hash', '');
$id = $request->input('id', '');
$result = app(TradeService::class)->buy($payment_address, $hash, $id);
return $this->success('success', $result);
} }
} }
...@@ -13,9 +13,15 @@ public function index() ...@@ -13,9 +13,15 @@ public function index()
{ {
// 铭文交易页面 // 铭文交易页面
$title = '铭文交易'; $title = '铭文交易';
// 获取买入手续费
$buy_fee = app(AdminConfigService::class)->getBuyFee();
// 收款账号
$receipt_account = app(AdminConfigService::class)->getReceiptAccount();
return Inertia::render('Trade/index', [ return Inertia::render('Trade/index', [
'info' => [ 'info' => [
'title' => $title, 'title' => $title,
'buy_fee' => $buy_fee,
'receipt_account' => $receipt_account
], ],
]); ]);
} }
......
...@@ -12,12 +12,7 @@ class InscriptionTrading extends Model ...@@ -12,12 +12,7 @@ class InscriptionTrading extends Model
protected $table = 'inscription_trading'; protected $table = 'inscription_trading';
protected $fillable = [ protected $fillable = [
'content_uri', 'inscription',
'quantity',
'owner',
'creator',
'inscription_created_date',
'inscription_hash',
'original_amount', 'original_amount',
'admin_account', 'admin_account',
'sell_fee', 'sell_fee',
...@@ -31,6 +26,12 @@ class InscriptionTrading extends Model ...@@ -31,6 +26,12 @@ class InscriptionTrading extends Model
'seller_receive_hash', 'seller_receive_hash',
'seller_cancel_hash', 'seller_cancel_hash',
'buyer_pay_hash', 'buyer_pay_hash',
'buyer_receive_inscription_hash' 'buyer_receive_inscription_hash',
'seller_address'
];
// 数组
protected $casts = [
'inscription' => 'array',
]; ];
} }
...@@ -10,4 +10,22 @@ class TransactionLog extends Model ...@@ -10,4 +10,22 @@ class TransactionLog extends Model
use HasFactory; use HasFactory;
protected $table = 'transaction_log'; protected $table = 'transaction_log';
// 可以修改的字段
protected $fillable = [
'to',
'from',
'hash',
'order_id',
'status',
'type',
'title',
'amount',
'error_message'
];
// 数组
protected $casts = [
'error_message' => 'array',
];
} }
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
use App\Exceptions\UserException; use App\Exceptions\UserException;
use Illuminate\Support\Facades\Log;
class CommonService class CommonService
{ {
...@@ -14,10 +15,29 @@ class CommonService ...@@ -14,10 +15,29 @@ class CommonService
// 自定义字段校验 // 自定义字段校验
public function customValidate($data, $fields, $messages = '校验未通过', $code_type = 1) public function customValidate($data, $fields, $messages = '校验未通过', $code_type = 1)
{ {
$params = [];
foreach ($fields as $field) { foreach ($fields as $field) {
if (blank($data[$field])) { if (array_key_exists($field, $data) && filled($data[$field])) {
// 添加数据
$params[$field] = $data[$field];
} else {
throw new UserException($code_type, $messages); throw new UserException($code_type, $messages);
} }
} }
return $params;
}
// 获取交易检测回调地址
public function getTradeCheckCallbackUrl()
{
$notify_url = '';
// 判断是否本地环境
if (app()->environment('local')) {
$notify_url = 'http://192.168.1.5:8080/api/inscription/admin/check';
} else {
// 当前域名
$notify_url = config('app.url') . '/api/inscription/admin/check';
}
return $notify_url;
} }
} }
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
use App\Exceptions\UserException; use App\Exceptions\UserException;
use App\Models\InscriptionTrading; use App\Models\InscriptionTrading;
use App\Service\Common\CommonService; use App\Service\Common\CommonService;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
class TradeService class TradeService
{ {
...@@ -33,6 +35,38 @@ class TradeService ...@@ -33,6 +35,38 @@ class TradeService
public const BUYER_RECEIVE_FAIL = 5; // 买家接收铭文失败(平台转移失败) public const BUYER_RECEIVE_FAIL = 5; // 买家接收铭文失败(平台转移失败)
public const BUYER_RECEIVE_SUCCESS = 6; // 买家接收铭文成功 public const BUYER_RECEIVE_SUCCESS = 6; // 买家接收铭文成功
// 交易类型
public const TRADE_TYPE_INSCRIPTION = 1; // 铭文转移
public const TRADE_TYPE_PAY = 2; // 支付
// 日志的交易状态
public const TRADE_STATUS_WAIT = 1; // 未开始
public const TRADE_STATUS_SUCCESS = 2; // 已完成
public const TRADE_STATUS_FAIL = 3; // 失败
public const TRADE_STATUS_FAIL_2 = 4; // 支付成功,接收地址错误
public const TRADE_STATUS_ERROR = 5; // 检测失败
// 交易名称(具体类型)
public const TRADE_NAME_SELLER_INSCRIPTION = 1; // 卖家铭文转移
public const TRADE_NAME_SELLER_CANCEL = 2; // 卖家取消(下架)
public const TRADE_NAME_SELLER_RECEIPT = 3; // 卖家收款
public const TRADE_NAME_BUYER_PAY = 4; // 买家支付
public const TRADE_NAME_BUYER_RECEIVE = 5; // 买家接收铭文
public const TRADE_NAME_LIST = [
self::TRADE_NAME_SELLER_INSCRIPTION => '卖家铭文转移',
self::TRADE_NAME_SELLER_CANCEL => '卖家取消(下架)',
self::TRADE_NAME_SELLER_RECEIPT => '卖家收款',
self::TRADE_NAME_BUYER_PAY => '买家支付',
self::TRADE_NAME_BUYER_RECEIVE => '买家接收铭文',
];
// redis key
public const REDIS_KEY_RECEIPT = 'admin_check_receipt'; // 到账查询
// 查询
public function model() public function model()
{ {
return new InscriptionTrading(); return new InscriptionTrading();
...@@ -44,44 +78,168 @@ public function create($data) ...@@ -44,44 +78,168 @@ public function create($data)
return $this->model()->create($data); return $this->model()->create($data);
} }
public function redisAdd($data, $key = self::REDIS_KEY_RECEIPT)
{
Redis::rpush($key, json_encode($data));
}
// 更新铭文数据
public function updateInscription($data)
{
// 铭文信息必须包含指定字段
$fields = ['transaction_hash', 'current_owner', 'content_uri', 'creator', 'creation_timestamp'];
// 校验铭文信息
$inscription = app(CommonService::class)->customValidate($data, $fields, '缺少必要的铭文信息');
return $inscription;
}
// 铭文列表
public function inscriptionList($page, $limit)
{
// 指定返回的字段
$field = ['id', 'inscription', 'status', 'original_amount'];
$model = $this->model()->where('status', self::ORDER_STATUS_ON)->where('buyer_address', null)
->orderBy('id', 'desc')->paginate($limit, $field, 'page', $page);
// 分页
return $model;
}
public function userShelves($payment_address, $hash, $inscription, $price) public function userShelves($payment_address, $hash, $inscription, $price)
{ {
if (blank($payment_address) || blank($hash) || blank($inscription)) { if (blank($payment_address) || blank($hash) || blank($inscription)) {
throw new UserException(1, '禁止访问'); throw new UserException(1, '非法操作');
} }
// 铭文信息必须包含指定字段 $inscription = self::updateInscription($inscription);
$fields = ['content_uri', 'quantity', 'owner', 'creator', 'inscription_created_date', 'inscription_hash'];
// 校验铭文信息
app(CommonService::class)->customValidate($inscription, $fields, '缺少必要的铭文信息');
// 重复的hash禁止上架 // 重复的hash禁止上架
$is_exist = $this->model()->where('hash', $hash)->first(); $is_exist = $this->model()->where('seller_transfer_inscription_hash', $hash)->first();
if ($is_exist) { if ($is_exist) {
throw new UserException(1, '禁止访问'); throw new UserException(1, '非法操作');
} }
// 卖家手续费比例 // 卖家手续费比例
$sell_fee = app(AdminConfigService::class)->getSellFee(); $sell_fee = app(AdminConfigService::class)->getSellFee();
// 手续费具体金额 // 手续费具体金额
$sell_fee_amount = $price * $sell_fee; $sell_fee_amount = $price * $sell_fee;
// 平台收款账号
$admin_address = app(AdminConfigService::class)->getReceiptAccount();
// 创建 // 创建
$data = [ $data = [
'content_uri' => $inscription['content_uri'], 'inscription' => $inscription,
'quantity' => $inscription['quantity'],
'owner' => $inscription['owner'],
'creator' => $inscription['creator'],
'inscription_created_date' => $inscription['inscription_created_date'],
'inscription_hash' => $inscription['inscription_hash'],
'original_amount' => $price, 'original_amount' => $price,
// 收款账号 // 收款账号
'admin_account' => app(AdminConfigService::class)->getReceiptAccount(), 'admin_account' => $admin_address,
'sell_fee' => $sell_fee, 'sell_fee' => $sell_fee,
'sell_fee_amount' => $sell_fee_amount, 'sell_fee_amount' => $sell_fee_amount,
'status' => self::ORDER_STATUS_WAIT, 'status' => self::ORDER_STATUS_WAIT,
'seller_status' => self::SELLER_STATUS_WAIT, 'seller_status' => self::SELLER_STATUS_PROGRESS,
'buyer_status' => self::BUYER_STATUS_WAIT, 'buyer_status' => self::BUYER_STATUS_WAIT,
'seller_transfer_inscription_hash' => $hash, 'seller_transfer_inscription_hash' => $hash,
// 卖家地址
'seller_address' => $payment_address,
]; ];
$status = $this->create($data); $result = $this->create($data);
if ($result) {
// 创建日志
$data['order_id'] = $result->id;
$data['trade_type'] = self::TRADE_TYPE_INSCRIPTION;
$data['title'] = self::TRADE_NAME_SELLER_INSCRIPTION;
$data['to'] = $admin_address;
$data['from'] = $payment_address;
$data['hash'] = $hash;
app(TransactionLogService::class)->create($data);
// 回调地址
$notify_url = app(CommonService::class)->getTradeCheckCallbackUrl();
// 铭文转移查铭文数据里的hash就可以了
$obj = [
'from' => $payment_address,
'to' => $admin_address,
'hash' => $hash,
'inscription_hash' => $inscription['transaction_hash'],
'id' => $result->id,
'type' => self::TRADE_TYPE_INSCRIPTION,
// log标题
'title' => self::TRADE_NAME_SELLER_INSCRIPTION,
'notify_url' => $notify_url
];
// 添加reids队列
self::redisAdd($obj);
return true;
}
throw new UserException(1, '上架失败');
}
// 卖家上架回调更新
public function sellerShelvesSuccess($data)
{
$status = $data['status'];
// 商品默认状态
$order_status = self::ORDER_STATUS_WAIT;
// 卖家状态
$seller_status = self::SELLER_STATUS_PROGRESS;
if ($status == self::TRADE_STATUS_SUCCESS) {
// 改为已上架
$order_status = self::ORDER_STATUS_ON;
// 卖家转移成功
$seller_status = self::SELLER_STATUS_SUCCESS;
} elseif ($status == self::TRADE_STATUS_FAIL || $status == self::TRADE_STATUS_FAIL_2) {
// 支付失败
$seller_status = self::SELLER_STATUS_FAIL;
}
$update = ['status' => $order_status, 'seller_status' => $seller_status];
$inscription = $data['inscription'] ?? null;
// 更新铭文信息
if ($inscription) {
$inscription = self::updateInscription($data['inscription']);
$update['inscription'] = $inscription;
}
$result = $this->model()->where('id', $data['id'])->update($update);
if ($result) {
return true;
}
throw new UserException(1, '操作失败');
}
// 买家支付修改状态
public function buyerPaymentStatus()
{
}
// 到账检测回调
public function check($data)
{
// 更新日志
app(TransactionLogService::class)->update($data);
// 根据日志标题更新市场列表
$title_type = $data['title'];
if ($title_type == self::TRADE_NAME_SELLER_INSCRIPTION) {
// 卖家铭文转移
self::sellerShelvesSuccess($data);
} else if ($title_type == self::TRADE_NAME_BUYER_PAY) {
// 买家支付
self::buyerPaymentStatus();
}
return true; return true;
} }
// 铭文交易记录
public function search($address, $type, $page, $limit)
{
if (blank($address) || blank($type)) {
throw new UserException(1, '非法操作');
}
$model = $this->model()->where('status', $type)->where('seller_address', $address)
->orderBy('id', 'desc')->paginate($limit, ['inscription', 'status', 'original_amount', 'sell_fee', 'seller_receive_hash'], 'page', $page);
// 分页
return $model;
}
// 买家支付
public function buy($payment_address, $hash, $id)
{
if (blank($payment_address) || blank($hash) || blank($id)) {
throw new UserException(1, '非法操作');
}
// 加字段,卖家卖出时收款地址,买家 购买时收款地址
}
} }
<?php
namespace App\Service;
use App\Models\TransactionLog;
use Illuminate\Support\Facades\Log;
class TransactionLogService
{
// 新增日志
public function create($params)
{
$data = [];
$data['to'] = $params['to'];
$data['from'] = $params['from'];
$data['hash'] = $params['hash'];
$data['order_id'] = $params['order_id'];
// 状态
$data['status'] = TradeService::TRADE_STATUS_WAIT;
$data['type'] = $params['trade_type'];
$data['title'] = $params['title'];
// amount
// error_message
TransactionLog::query()->create($data);
}
public function update($data)
{
// callback status 1:失败 2:成功 3:支付成功,接收地址错误
$model = TransactionLog::query()->where('order_id', $data['id'])
->where('type', $data['type'])
->where('to', $data['to'])
->where('from', $data['from'])
->where('hash', $data['hash']);
if ($data['type'] == TradeService::TRADE_TYPE_PAY) {
// 添加金额
$model->where('amount', $data['amount']);
}
$model->update(['status' => $data['status']]);
}
}
...@@ -16,19 +16,9 @@ public function up() ...@@ -16,19 +16,9 @@ public function up()
Schema::create('inscription_trading', function (Blueprint $table) { Schema::create('inscription_trading', function (Blueprint $table) {
$table->id(); $table->id();
// 铭文数据 // 铭文数据
$table->text('content_uri')->comment('铭文数据(data:json)'); $table->text('inscription')->comment('铭文数据');
// 数量(amt)
$table->integer('quantity')->comment('总数量');
// 当前持有者
$table->string('owner')->comment('当前持有者');
// 创建者
$table->string('creator')->comment('创建者');
// 铭文创建时间
$table->dateTime('inscription_created_date')->comment('铭文创建时间');
// 铭文上的原始hash
$table->string('inscription_hash')->comment('铭文上的hash');
// 原始金额--卖家输入的 // 原始金额--卖家输入的
$table->integer('original_amount')->comment('原始金额'); $table->decimal('original_amount', 20, 8)->comment('原始金额');
// 后台收款账号 // 后台收款账号
$table->string('admin_account')->comment('后台收款账号'); $table->string('admin_account')->comment('后台收款账号');
// 卖出手续费比例 // 卖出手续费比例
...@@ -57,6 +47,10 @@ public function up() ...@@ -57,6 +47,10 @@ public function up()
$table->string('buyer_pay_hash')->nullable()->comment('买家支付hash'); $table->string('buyer_pay_hash')->nullable()->comment('买家支付hash');
// 买家接收铭文hash // 买家接收铭文hash
$table->string('buyer_receive_inscription_hash')->nullable()->comment('买家接收铭文hash'); $table->string('buyer_receive_inscription_hash')->nullable()->comment('买家接收铭文hash');
// 卖家地址
$table->string('seller_address')->nullable()->comment('卖家地址');
// 买家地址
$table->string('buyer_address')->nullable()->comment('买家地址');
$table->timestamps(); $table->timestamps();
}); });
......
...@@ -14,6 +14,8 @@ public function up() ...@@ -14,6 +14,8 @@ public function up()
{ {
Schema::create('transaction_log', function (Blueprint $table) { Schema::create('transaction_log', function (Blueprint $table) {
$table->id(); $table->id();
// 订单id(对应市场表的id)
$table->integer('order_id')->comment('订单id');
// to // to
$table->string('to')->comment('收款人地址'); $table->string('to')->comment('收款人地址');
//from //from
...@@ -22,8 +24,10 @@ public function up() ...@@ -22,8 +24,10 @@ public function up()
$table->string('hash')->comment('交易hash'); $table->string('hash')->comment('交易hash');
// 状态 // 状态
$table->unsignedTinyInteger('status')->default(0)->comment('交易状态'); $table->unsignedTinyInteger('status')->default(0)->comment('交易状态');
// 交易类型 // 交易类型(铭文,转账)
$table->unsignedTinyInteger('type')->default(0)->comment('交易类型'); $table->unsignedTinyInteger('type')->default(0)->comment('交易类型');
// title
$table->integer('title')->default(0)->comment('交易标题');
// 交易金额 // 交易金额
$table->decimal('amount', 20, 8)->default(0)->comment('交易金额'); $table->decimal('amount', 20, 8)->default(0)->comment('交易金额');
// 错误信息 // 错误信息
......
...@@ -26,6 +26,20 @@ ...@@ -26,6 +26,20 @@
</div> </div>
<div class="sell-inscription-tips"> <div class="sell-inscription-tips">
<div class="label">铭文托管转移</div> <div class="label">铭文托管转移</div>
<div class="all-address">
<div>
<span class="all-address-label">从 : </span>
<span class="all-address-value">{{
userAddress.address
}}</span>
</div>
<div>
<span class="all-address-label">到 : </span>
<span class="all-address-value">{{
app_info.receipt_account
}}</span>
</div>
</div>
<div class="value"> <div class="value">
注意事项:由于还未有开放的交易市场,平台所有交易都是以OTC方式进行交易,卖家的铭文 注意事项:由于还未有开放的交易市场,平台所有交易都是以OTC方式进行交易,卖家的铭文
必须托管转移到平台账户,才可进行挂单出售,平台匹配到对应的买家,则会把出售的USDT 必须托管转移到平台账户,才可进行挂单出售,平台匹配到对应的买家,则会把出售的USDT
...@@ -65,6 +79,7 @@ import { showMessage } from "@/utils/tool"; ...@@ -65,6 +79,7 @@ import { showMessage } from "@/utils/tool";
import { inertia_data } from "@/constants/token"; import { inertia_data } from "@/constants/token";
import { inscriptionTransfer } from "@/utils/ethers"; import { inscriptionTransfer } from "@/utils/ethers";
import { useStore } from "vuex"; import { useStore } from "vuex";
import { sellerInscriptions } from "@/utils/api/index";
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
...@@ -74,7 +89,7 @@ const props = withDefaults( ...@@ -74,7 +89,7 @@ const props = withDefaults(
}>(), }>(),
{} {}
); );
const emit = defineEmits(["update:modelValue"]); const emit = defineEmits(["update:modelValue", "updateList"]);
const store = useStore(); const store = useStore();
const userAddress = computed(() => store.getters["user/address"]); const userAddress = computed(() => store.getters["user/address"]);
const visible = ref(props.modelValue); const visible = ref(props.modelValue);
...@@ -111,7 +126,29 @@ const onCanel = () => { ...@@ -111,7 +126,29 @@ const onCanel = () => {
visible.value = false; visible.value = false;
}; };
const onSubmit = () => { // 提交到后台
const submitAdmin = async (
data: any,
price: number | string,
current_inscription: any
) => {
try {
const res: any = await sellerInscriptions({
payment_address: data.from,
hash: data.hash,
price: price,
inscription: current_inscription,
});
if (res.code == 0) {
showMessage("已提交,请等待", "success");
return true;
}
} catch (e) {
console.log(e);
}
};
const onSubmit = async () => {
const { info } = props; const { info } = props;
if (!price.value) { if (!price.value) {
showMessage("请输入价格"); showMessage("请输入价格");
...@@ -137,13 +174,26 @@ const onSubmit = () => { ...@@ -137,13 +174,26 @@ const onSubmit = () => {
} }
// data数据 // data数据
let data = info.content_uri; let data = info.transaction_hash;
if (!data) { if (!data) {
showMessage("没有data数据"); showMessage("没有data数据");
return; return;
} }
// 记录本次提交的价格,防止被修改
let current_price = price.value;
// 本次铭文信息
let current_inscription = props.info;
// 可以转移 // 可以转移
inscriptionTransfer(receipt_account, from, data); const res = await inscriptionTransfer(receipt_account, from, data);
if (res && res.hash) {
let status = await submitAdmin(res, current_price, current_inscription);
if (status) {
// 关闭弹窗
visible.value = false;
// 通知页面更新我的铭文列表
emit("updateList");
}
}
}; };
</script> </script>
...@@ -195,7 +245,20 @@ const onSubmit = () => { ...@@ -195,7 +245,20 @@ const onSubmit = () => {
.value { .value {
font-size: 11px; font-size: 11px;
color: #858585; color: #858585;
margin: 50px 0 12px 0; margin: 20px 0 12px 0;
}
.all-address {
font-size: 15px;
color: #000000;
& > * {
margin-top: 6px;
}
.all-address-label {
font-weight: 700;
}
.all-address-value {
font-weight: 400;
}
} }
} }
.service-fee-box { .service-fee-box {
......
<template>
<t-dialog
v-model:visible="visible"
placement="center"
class="custom-confirm-dialog"
width="485px"
>
<template #header> </template>
<template #body>
<div class="custom-confirm-content">
<div class="confirm-title">确定取消订单?</div>
<div class="confirm-footer">
<t-button
class="confirm-footer-canenl"
@click="visible = false"
>取消</t-button
>
<t-button class="confirm-footer-submit" @click="onSubmit"
>确认</t-button
>
</div>
</div>
</template>
<template #footer> </template>
</t-dialog>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
const props = withDefaults(
defineProps<{
modelValue: boolean;
}>(),
{}
);
const emit = defineEmits(["update:modelValue", "ConfirmCancel"]);
const visible = ref(props.modelValue);
watch(
() => props.modelValue,
(v) => {
visible.value = v;
}
);
watch(
() => visible.value,
(v) => {
emit("update:modelValue", v);
}
);
const onSubmit = () => {
emit("ConfirmCancel");
visible.value = false;
};
</script>
<style lang="less">
.custom-confirm-dialog {
.t-dialog {
padding: 0;
border-radius: 8px;
.t-dialog__header {
height: 45px;
padding: 0 20px;
}
.custom-confirm-content {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.confirm-title {
color: #000000;
font-size: 20px;
}
.confirm-footer {
margin-top: 20px;
.confirm-footer-canenl {
width: 80px;
height: 40px;
border-radius: 5px;
border: 1px solid #dedede;
background: transparent;
color: #000000;
--ripple-color: #ddd !important;
margin-right: 20px;
}
.confirm-footer-submit {
width: 80px;
height: 40px;
border-radius: 5px;
border: 1px solid #dedede;
background: #235ffa;
color: #ffffff;
border-color: #235ffa;
}
}
}
}
}
</style>
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
<div class="service-fee-box"> <div class="service-fee-box">
<div class="service-fee-line"> <div class="service-fee-line">
<div class="label">价值</div> <div class="label">价值</div>
<div class="value">${{ info.price }}</div> <div class="value">${{ info.original_amount }}</div>
</div> </div>
<div class="service-fee-line"> <div class="service-fee-line">
<div class="label">服务费</div> <div class="label">服务费</div>
...@@ -32,16 +32,25 @@ ...@@ -32,16 +32,25 @@
</template> </template>
<template #footer> <template #footer>
<div class="buy-dialog-footer"> <div class="buy-dialog-footer">
<t-button class="buy-cancel-button">取消</t-button> <t-button class="buy-cancel-button" @click="visible = false"
<t-button class="buy-confirm-button">确认</t-button> >取消</t-button
>
<t-button class="buy-confirm-button" @click="onSubmit"
>确认</t-button
>
</div> </div>
</template> </template>
</t-dialog> </t-dialog>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, ref, watch } from "vue"; import { computed, inject, ref, watch } from "vue";
import CustomCard from "@/components/card.vue"; import CustomCard from "@/components/card.vue";
import { fox_EtherPay } from "@/utils/ethers";
import { useStore } from "vuex";
import { inertia_data } from "@/constants/token";
import { showMessage } from "@/utils/tool";
import { buyerPaymentSuccess } from "@/utils/api";
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
...@@ -52,21 +61,67 @@ const props = withDefaults( ...@@ -52,21 +61,67 @@ const props = withDefaults(
{} {}
); );
const emit = defineEmits(["update:modelValue"]); const emit = defineEmits(["update:modelValue"]);
const store = useStore();
const visible = ref(props.modelValue); const visible = ref(props.modelValue);
// app
const app_info = inject(inertia_data);
// 点击确认时记录输入框输入的价格,防止被修改 // 点击确认时记录输入框输入的价格,防止被修改
const confirmPrice = ref(""); const confirmPrice = ref("");
const userAddress = computed(() => store.getters["user/address"]);
// 根据输入的价格计算可获得usdt // 根据输入的价格计算可获得usdt
const realPrice = computed(() => { const realPrice = computed(() => {
const { fee, info } = props; const { fee, info } = props;
if (info.price && fee) { if (info.original_amount && fee) {
return ( return (
parseFloat(info.price + "") + parseFloat(info.original_amount + "") +
info.price * (parseFloat(fee + "") / 100) info.original_amount * (parseFloat(fee + "") / 100)
); );
} }
}); });
//
const submitAdmin = async (
hash: string,
address: string,
id: string | number
) => {
try {
let res: any = await buyerPaymentSuccess({
payment_address: address,
hash: hash,
id: id,
});
if (res.code == 0) {
showMessage("已提交,请等待");
return;
}
} catch (e) {
console.log(e);
}
};
// 立即购买
const onSubmit = async () => {
// 付款账号
let address = userAddress.value.address;
// id
let id = props.info.id;
// 收款地址
let receipt_account = app_info.receipt_account;
if (!receipt_account) {
showMessage("缺少必要的数据");
return;
}
const hash = await fox_EtherPay(receipt_account, realPrice.value);
if (hash) {
// 提交到后台校验
submitAdmin(hash, address, id);
}
console.log(hash);
};
watch( watch(
() => props.modelValue, () => props.modelValue,
(v) => { (v) => {
......
...@@ -18,7 +18,9 @@ ...@@ -18,7 +18,9 @@
<CustomCard :cardData="item"> <CustomCard :cardData="item">
<template #footer> <template #footer>
<div class="buy-card-footer"> <div class="buy-card-footer">
<div class="price">${{ item.price }}</div> <div class="price">
${{ item.original_amount }}
</div>
<t-button <t-button
@click="buyNow(item)" @click="buyNow(item)"
class="buy-now-button" class="buy-now-button"
...@@ -33,30 +35,35 @@ ...@@ -33,30 +35,35 @@
<BuyDialog <BuyDialog
v-model="dialog_info.status" v-model="dialog_info.status"
:info="dialog_info.info" :info="dialog_info.info"
:fee="current_fee" :fee="app_info.buy_fee"
></BuyDialog> ></BuyDialog>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import CustomSelect from "@/components/select.vue"; import CustomSelect from "@/components/select.vue";
import { ref, reactive, onMounted } from "vue"; import { ref, reactive, onMounted, inject } from "vue";
import CustomCard from "@/components/card.vue"; import CustomCard from "@/components/card.vue";
import { test_wallet_list } from "@/constants/testData"; import { test_wallet_list } from "@/constants/testData";
import { isDev } from "@/utils/tool"; import { isDev } from "@/utils/tool";
import { inscriptionListFilter } from "@/utils/api/public"; import {
inscriptionListFilter,
inscriptionListAdminFilter,
} from "@/utils/api/public";
import BuyDialog from "./BuyDialog.vue"; import BuyDialog from "./BuyDialog.vue";
import { getMarketInscriptionList } from "@/utils/api";
import { inertia_data } from "@/constants/token";
const imgs = { const imgs = {
eth: new URL("../../../assets/svg/trade/eth2.svg", import.meta.url).href, eth: new URL("../../../assets/svg/trade/eth2.svg", import.meta.url).href,
}; };
// 当前select // 当前select
const selectValue = ref("asc"); const selectValue = ref("asc");
// 接收app数据
const app_info = inject(inertia_data);
const dialog_info = reactive({ const dialog_info = reactive({
status: false, status: false,
info: {}, info: {},
}); });
// 手续费
const current_fee = ref("1");
const options = [ const options = [
{ {
label: "从低到高", label: "从低到高",
...@@ -82,30 +89,28 @@ const buyNow = (item: any) => { ...@@ -82,30 +89,28 @@ const buyNow = (item: any) => {
}; };
const listResult = (list: any[]) => { const listResult = (list: any[]) => {
// 过滤数据 // 过滤数据
list = inscriptionListFilter(list); list = inscriptionListAdminFilter(list);
// 添加测试数据
list.forEach((item: any) => { list.forEach((item: any) => {
item.price = "300"; item.original_amount = parseFloat(item.original_amount + "");
}); });
tableList.list = inscriptionListFilter(list); tableList.list = list;
tableList.total = list.length; tableList.total = list.length;
}; };
const getList = async () => { const getList = async () => {
try { try {
if (isDev()) { tableList.loading = true;
// 本地环境 const res: any = await getMarketInscriptionList({
listResult(test_wallet_list); page: tableList.pageNum,
} else { limit: tableList.pageSize,
tableList.loading = true; });
const res: any = await getUserInscriptionList(address); if (res.code == 0) {
if (res) { if (res.data.data.length) {
let list = JSON.parse(res); listResult(res.data.data);
if (list.length) { } else {
listResult(list); listResult([]);
}
} }
tableList.loading = false;
} }
tableList.loading = false;
} catch (e) { } catch (e) {
console.log(e); console.log(e);
tableList.loading = false; tableList.loading = false;
......
...@@ -18,7 +18,7 @@ import { Head } from "@inertiajs/vue3"; ...@@ -18,7 +18,7 @@ import { Head } from "@inertiajs/vue3";
import Navbar from "@/components/navbar.vue"; import Navbar from "@/components/navbar.vue";
import CustomTable from "@/components/table.vue"; import CustomTable from "@/components/table.vue";
import CustomInput from "@/components/input/index.vue"; import CustomInput from "@/components/input/index.vue";
import { reactive, ref } from "vue"; import { reactive, ref, provide } from "vue";
import CustomGroupButton from "@/components/groupButton.vue"; import CustomGroupButton from "@/components/groupButton.vue";
import TipsSvg from "@/assets/svg/content/tips.svg"; import TipsSvg from "@/assets/svg/content/tips.svg";
import CustomTooltip from "@/components/tooltip.vue"; import CustomTooltip from "@/components/tooltip.vue";
...@@ -30,9 +30,11 @@ import TradeHeader from "./components/TradeHeader.vue"; ...@@ -30,9 +30,11 @@ import TradeHeader from "./components/TradeHeader.vue";
import CustomTabs from "@/components/tabs.vue"; import CustomTabs from "@/components/tabs.vue";
import MarketVue from "./components/Market.vue"; import MarketVue from "./components/Market.vue";
import TradeLog from "./components/TradeLog.vue"; import TradeLog from "./components/TradeLog.vue";
defineProps({ info: Object }); import { inertia_data } from "@/constants/token";
const props = defineProps({ info: Object });
// 请求接口放这里 //注入
provide(inertia_data, props.info);
const tabList = [ const tabList = [
{ {
......
...@@ -42,6 +42,7 @@ ...@@ -42,6 +42,7 @@
</div> </div>
</div> </div>
<slot name="footer"> </slot> <slot name="footer"> </slot>
<slot name="pos"> </slot>
</div> </div>
</template> </template>
...@@ -61,6 +62,7 @@ const props = withDefaults( ...@@ -61,6 +62,7 @@ const props = withDefaults(
border: 1px solid #ebebeb; border: 1px solid #ebebeb;
box-sizing: border-box; box-sizing: border-box;
box-shadow: 0px 4px 25px 10px #0000000d; box-shadow: 0px 4px 25px 10px #0000000d;
position: relative;
.inscription-card-head { .inscription-card-head {
background: rgb(250, 250, 250); background: rgb(250, 250, 250);
......
...@@ -10,3 +10,26 @@ export const getUserInscriptionList = (address: string) => { ...@@ -10,3 +10,26 @@ export const getUserInscriptionList = (address: string) => {
}; };
// 卖家上架铭文 // 卖家上架铭文
export const sellerInscriptions = (data: any) => {
return request.post("/api/inscription/shelves", data);
};
// 卖家交易记录
export const sellerTradeLog = (data: any) => {
return request.get("/api/inscription/sell/log", {
params: data,
});
};
// 获取市场上的铭文交易列表
export const getMarketInscriptionList = (data: any) => {
return request.get("/api/inscription/market", {
params: data,
});
};
// 买家支付
export const buyerPaymentSuccess = (data: any) => {
//
return request.post("/api/inscription/buy", data);
};
import { getMask, timeFormatSeconds, getCreatedHowLongAgo } from "@/utils/tool"; import { getMask, timeFormatSeconds, getCreatedHowLongAgo } from "@/utils/tool";
// 铭文列表过滤 // 铭文列表过滤 外部接口的
export const inscriptionListFilter = (list: any[]) => { export const inscriptionListFilter = (list: any[]) => {
list.forEach((item: any) => { list.forEach((item: any) => {
// 创建者,掩码 // 创建者,掩码
...@@ -29,3 +29,48 @@ export const inscriptionListFilter = (list: any[]) => { ...@@ -29,3 +29,48 @@ export const inscriptionListFilter = (list: any[]) => {
}); });
return list; return list;
}; };
// 铭文列表过滤,平台内部接口
export const inscriptionListAdminFilter = (list: any[]) => {
try {
list.forEach((item: any) => {
let inscription = item.inscription;
// 创建者,掩码
item.new_creator = getMask(inscription.creator, 6, 4);
// 当前持有,掩码
item.new_current_owner = getMask(inscription.current_owner, 4, 4);
item.creation_timestamp = timeFormatSeconds(
inscription.creation_timestamp
);
// 距离当前时间多久
item.created_time = getCreatedHowLongAgo(
inscription.creation_timestamp
);
// hash
item.new_transaction_hash = getMask(
inscription.transaction_hash,
4,
4
);
// 去掉换行符和空格
item.new_content_uri = inscription.content_uri.replace(/\r\n/g, "");
// 过滤出名称和数量
const match = inscription.content_uri.match(/{.*}/);
if (match) {
const jsonStr = match[0];
const jsonObject = JSON.parse(jsonStr);
item.name = jsonObject.tick;
item.number = jsonObject.amt;
item.id = jsonObject.id;
} else {
item.image = inscription.content_uri;
console.log("content_uri格式错误");
console.log(inscription.content_uri);
}
});
} catch (e) {
console.log(e);
}
return list;
};
...@@ -46,23 +46,28 @@ export const inscriptionTransfer = async ( ...@@ -46,23 +46,28 @@ export const inscriptionTransfer = async (
data: any data: any
) => { ) => {
// data转16进制 // data转16进制
let data16 = ConvertToHexadecimal(data); // let data16 = ConvertToHexadecimal(data);
console.log(data16);
try { try {
const res: any = await eth.ethereum.request({ const hash: any = await eth.ethereum.request({
method: "eth_sendTransaction", method: "eth_sendTransaction",
params: [ params: [
{ {
from: from, from: from,
to: to, to: to,
value: 0, value: 0,
data: data16, data: data,
// gasLimit: "0x5028", // gasLimit: "0x5028",
// maxPriorityFeePerGas: "0x3b9aca00", // maxPriorityFeePerGas: "0x3b9aca00",
// maxFeePerGas: "0x2540be400", // maxFeePerGas: "0x2540be400",
}, },
], ],
}); });
if (hash) {
return {
hash: hash,
from: from,
};
}
// 获取交易hash // 获取交易hash
} catch (e: any) { } catch (e: any) {
console.log(e); console.log(e);
......
...@@ -28,6 +28,14 @@ ...@@ -28,6 +28,14 @@
Route::get('/ethscriptions/owned_by', 'InscriptionController@wallet'); Route::get('/ethscriptions/owned_by', 'InscriptionController@wallet');
// 铭文上架 // 铭文上架
Route::post('/shelves', 'TradeController@shelves'); Route::post('/shelves', 'TradeController@shelves');
// 交易检测回调
Route::post('/admin/check', 'TradeController@check');
// 卖家交易记录
Route::get('/sell/log', 'TradeController@search');
// 获取市场上的铭文列表
Route::get('/market', 'TradeController@inscriptionList');
// buy
Route::post('/buy', 'TradeController@buy');
}); });
// // 需要登录 // // 需要登录
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment