原文轉載自「劉悅的技術部落格」https://v3u.cn/a_id_219
不得不承認,大多數人並不擁有或者曾經擁有加密貨幣。是的,Web3.0、加密貨幣、區塊鏈,對於大多數的網際網路使用者來說,其實是一些過於輕佻的詞彙。如果你是為了追求暴利投機而研究區塊鏈和加密貨幣,那你多半會失望,因為鹽在哪裡都是鹹的;而如果你是為了擺脫知識桎梏而學習區塊鏈,那你幾乎一定能滿足,因為懵懂決不是程式設計界的常態。
對於支付系統來說,加密貨幣的主要好處之一是去中心化,這意味著它由許多團隊或團體控制和管理,而不是一個單一的中心化機構,暗箱操作在這裡並不存在,這為系統帶來了透明度。加密貨幣的另一個好處是它是一個點對點系統,因為它可以傳送給世界上任何人,消除了微信、支付寶等第三方支付寡頭的干預,這使得它們具有成本效益。
本次,就讓我們來為支付系統添上加密貨幣支付的這一筆,透過Vue3.0+Tornado6的前後端分離系統,一睹區塊鏈加持下去中心化支付邏輯的風采。
前期準備
首先,我們當然需要一個加密貨幣錢包,關於系統整合MetaMask錢包的邏輯,請參見之前的一篇:青山不遮,畢竟東流,整合Web3.0身份錢包MetaMask以太坊一鍵登入(Tornado6+Vue.js3)。
其後,我們需要為接下來的支付行為領取一些“測試加密貨幣”,領取測試幣的網站被稱為水龍頭(faucet),測試幣被稱為水,所以領取測試幣的過程也被叫做領水。
我們以 Rinkeby 的測試幣領取為例講解過程,其他測試網的測試幣領取方式類似,如果願意,大家可以把幾個測試網的水都領一些。
第一步,開啟錢包外掛,選擇一個錢包,點選設定:
隨後,選擇高階,然後啟用測試網路:
接著,將網路切換到Rinkeby測試網路,網路中還可以看到 Ropsten、Kovan和 Goerli等其他三個測試網路,四個測試網路各有特點, Ropsten 採用 POW 機制,可以自己搭建節點挖測試幣,但是穩定性較差,偶爾還會遇到區塊回滾的情況,很多實驗性測試,比如 “區塊阻塞攻擊” 實驗會放到這個測試網來測試; Kovan 、Rinkeby 和 Goerli 是採用 POA 機制,這幾個測試網路不能透過挖礦的方式獲取測試幣,只能透過水龍頭領取,我們以 Rinkeby 為例講解領取過程, Kovan 和 Goerli 類似,但領取條件更為嚴格,大家可以根據需要領取。
切換好Rinkeby測試網路後,訪問水龍頭網站:https://faucets.chain.link/rinkeby 透過錢包進行連結登入,然後將錢包地址填入領取表單,即可領取0.1的eth貨幣:
領取交易確認之後,檢視錢包餘額:
至此,前期準備工作就完成了。
錢包支付加簽
前端在Vue3.0專案中安裝區塊鏈模組和非同步請求模組:
npm install --save ethers
npm install --save axios
隨後在元件中匯入區塊鏈模組:
import {ethers} from 'ethers';
並且對axios進行簡單封裝:
const myaxios = function(url,type,data={}){
return new
Promise((resolve) => {
//判斷
if(type==="get" || type === "delete"){
axios({
method:type,
url:url,
params:data
}).then((result) => {
resolve(result.data);
});
}else{
axios({
method:type,
url:url,
data:qs.stringify(data)
}).then((result) => {
resolve(result.data);
});
}
});
}
const app = createApp(App)
app.config.globalProperties.myaxios = myaxios;
接著,當頁面首次載入時,我們希望檢查使用者是否已經將錢包連線到站點。為此,我們需要使用eth_accounts方法獲取使用者的帳戶。如果沒有返回帳戶,這意味著使用者沒有連線:
checkIfWalletConnected:function() {
window.ethereum.request({ method: 'eth_accounts' }).then(function (accounts) {
if (accounts.length > 0) {
console.log(accounts[0]);
} else {
alert("應用未連結錢包");
}
});
}
隨後,在初始化方法內對當前使用者進行檢測:
created(){
this.checkIfWalletConnected();
}
如果使用者錢包連結沒問題,那麼構建支付表單:
<input type="text" v-model="amount" />
<a-button type="primary" @click="create_sign">點選支付</a-button>
這裡使用者點選支付按鈕後,進入加簽邏輯:
create_sign:function(){
var provider = new ethers.providers.Web3Provider(web3.currentProvider);
//獲取簽名物件
var signer = provider.getSigner();
//時間戳
var rightnow = (Date.now()/1000).toFixed(0);
var sortanow = rightnow - (rightnow % 600);
//生成簽名
signer.signMessage("Trade with "+document.domain+" at "+sortanow+" for "+this.amount,this.accountaddress,"test").then((signature) => {
this.check_sign(signature);
});
}
這裡透過Web3Provider獲取到簽名例項,隨後根據時間戳+域名+支付金額生成簽名,簽名生成後,立刻呼叫check_sign方法向後臺發起非同步請求進行驗籤操作:
check_sign:function(signature){
this.myaxios(this.weburl+"/sign/","post",{"signature":signature,"accountaddress":this.accountaddress,"amount":this.amount}).then(data =>{
if(data.errcode == 0){
this.makePaymentRequest(data.data.selleraddress,data.data.amount);
}
});
}
這裡將簽名和錢包地址傳送給後端,在客戶端與錢包請求互動的過程中,請求的資料很容易被攔截並篡改,所以加簽環節必不可少:
後端驗籤並建立交易
後端需要web3模組的加持:
pip3 install web3
隨後建立驗籤方法:
from web3.auto import w3
# 反編譯方法
from eth_account.messages import defunct_hash_message
import time
class CheckSign(BaseHandler):
async def post(self):
signature = self.get_argument("signature",None)
accountaddress = self.get_argument("accountaddress",None)
amount = self.get_argument("amount",None)
selleraddress = "0x95f57Bf3837325FE99a611EFacff6b1d70C7731A"
# 獲取當前域名
domain = self.request.host
if ":" in domain:
domain = domain[0:domain.index(":")]
# 時間戳
now = int(time.time())
sortanow = now - now % 600
# 生成簽名
message = "Trade with {} at {} for {}".format(domain,sortanow,amount)
print(message)
# 反編譯
message_hash = defunct_hash_message(text=message)
# 獲取簽名物件
signer = w3.eth.account.recoverHash(message_hash,signature=signature)
print(accountaddress,signer)
if accountaddress == signer.lower():
res = {"errcode":0,"msg":"ok","data":{"selleraddress":selleraddress,"amount":str(w3.toWei(amount,'ether'))}}
else:
res = {"errcode":1,"msg":"failed"}
self.finish(res)
這裡後端透過同樣的演算法對簽名進行驗證,如果驗籤透過,後端將會返回商戶的錢包地址,也就是使用者轉賬的錢包地址,同時會將付款金額透過w3.toWei方法進行轉換,以太幣的最小單位為wei,1個以太幣相當於10的8次方wei。通常,大家也使用Gwei作為展示單位。比較常用的就是eth,Gwei和wei。
但為了統一標準,支付表單彙總顯示的是eth最大單位,所以透過toWei方法,將最大單位轉換為最小單位,即0.001eth=100000000000000wei,注意轉換後需要以字串的形式返回到前端。
隨後後端將商戶錢包地址和轉換後的支付金額返回給前端。
建立交易
回到前端,驗籤透過後,前端獲取支付錢包地址和金額,旋即透過錢包建立支付:
makePaymentRequest:function(sellerAddress,amount) {
window.ethereum.request({ method:'eth_sendTransaction', params: [{ from:this.accountaddress, to:sellerAddress,value:amount}] })
.then(response => {
console.log("交易號:"+response);
var orderid = response;
})
.catch(error => {
console.log(error);
});
}
透過eth_sendTransaction方法發起交易,當使用者同意支付請求時,將會返回此筆交易的TransactionHash,也就是交易雜湊號:
確認支付交易後,獲取TransactionHash:
交易號:0xe937c66e337322cf3b83788b495af2da35ff9635aaaa20f156c74c7f7fddad26
事實上,每一筆支付交易都會產生另一筆“燃料費”,交易燃料費將歸屬於挖出區跨鏈中本次交易區塊的礦工。當礦工挖礦時,他需要決定哪些交易放入到區塊中,可以隨機選擇交易, 也可以不包含任何交易。為了鼓勵讓礦工將你的交易放入區塊,相應地,你必須付出一部分“小費”。
支付查詢
支付確認之後,我們可以利用Rinkeby網路站點透過輸入交易雜湊號來查詢這一筆交易:https://rinkeby.etherscan.io/tx/0xe937c66e337322cf3b83788b495af2da35ff9635aaaa20f156c74c7f7fddad26:
當然了,讓使用者自己在“水龍頭”上查詢支付結果顯然不怎麼講究,後端肯定需要記錄交易雜湊號並且查詢交易明細,這裡我們需要一個“上鍊”服務,讓我們的後端也接上區塊鏈網路,訪問 https://infura.io/
Infura是一種IaaS產品,目的是為了降低訪問以太坊資料的門檻。 對於開發者來說,Infura是一個可以讓你的dApp快速接入以太坊的平臺,不需要本地執行以太坊節點。 Infura背後是負載均衡的API節點叢集。 有針對以太坊Infura有一系列的開發套件。
註冊後,建立連結專案:
隨後,複製Rinkeby節點連結:
接著,建立訂單查詢指令碼 checkorder.py:
from web3 import Web3
w3 = Web3(Web3.HTTPProvider("https://rinkeby.infura.io/v3/32ff12c27a9c485db9a7b61b0a7f3f61"))
print(w3.isConnected())
print(w3.eth.getTransactionReceipt("0xe937c66e337322cf3b83788b495af2da35ff9635aaaa20f156c74c7f7fddad26"))
這裡一旦“上鍊”成功,就可以根據交易雜湊號來查詢交易的明細,系統返回:
True
AttributeDict({'blockHash': HexBytes('0x4ede42c4bd15c7ce1736523ae1f84284c7bbdc17388cfbae0df2897bf19f287c'), 'blockNumber': 11098045, 'contractAddress': None, 'cumulativeGasUsed': 13409523, 'effectiveGasPrice': 1500000017, 'from': '0x3B14DdBa7FFF887ED3CCF01fCa0b84501Fd7a711', 'gasUsed': 21000, 'logs': [], 'logsBloom': HexBytes('0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'), 'status': 1, 'to': '0x95f57Bf3837325FE99a611EFacff6b1d70C7731A', 'transactionHash': HexBytes('0xe937c66e337322cf3b83788b495af2da35ff9635aaaa20f156c74c7f7fddad26'), 'transactionIndex': 16, 'type': '0x2'})
至此,完成的加密貨幣支付邏輯就完成了,大體流程如下:
1. 應用會載入並自動檢查 Metamask 錢包是否已連線。如果沒有,將會提示使用者安裝錢包外掛並且連結。
2. 交易加簽操作。
3. 後端驗籤,並且返回商戶錢包地址以及轉換金額。
4. 錢包建立交易。
5. 使用者稽核並確認付款。
6. 使用者確認交易,生成交易號,使用者和應用都會收到付款確認。
退款
很遺憾,使用者在向錢包地址傳送加密貨幣時必須非常小心,如果有人將加密貨幣傳送到任何錯誤的地址,使用者將無法取消交易或提出任何投訴以獲得退款,是的,deal is deal,當交易行為已經被寫入區塊,那麼是無法被撤銷的。
但這並不意味的使用者就會因此和平臺商戶產生糾紛,如果溝通之後,達成了退款協議,加密貨幣也可以直接從後臺進行轉賬操作:
from web3 import Web3
import os
w3 = Web3(Web3.HTTPProvider("https://rinkeby.infura.io/v3/32ff12c27a9c485db9a7b61b0a7f3f61"))
print(w3.isConnected())
address1 = w3.toChecksumAddress('selleraddress')
address2 = w3.toChecksumAddress ('accountaddress')
private_key = os.getenv('PRIVATE_KEY')
# in this case, the nonce is the amount of transactions on accounti
nonce = w3.eth.getTransactionCount(address1)
# setup the transaction
tx ={
'nonce':nonce,
"to":address2,
'value':w3.toWei("0.0001","ether"),
"gas": 21000,
"gasPrice":w3.toWei(40,'gwei'),
}
signed_tx = w3.eth.account.signTransaction(tx, private_key)
tx_hash = w3.sendRawTransaction(signed_tx.rawTransaction)
這裡商戶只需要將錢包私鑰匯入環境變數,隨後建立交易並透過私鑰加簽,最後確認交易,並且獲取到交易雜湊號。
結語
毫無疑問,加密貨幣會損害一部分傳統行業既得利益者的利益,但也不能不承認,加密貨幣更是二十一世紀的一記響雷,就像洪荒時代孑餘的一頭恐龍、大戈壁中枝葉扶疏的一株胡楊、兵馬俑陣中一個脈搏跳動體溫猶存的肉身、死寂的山谷中憑空乍響的一聲洪鐘。所謂衣不如新,人不如故,不入春園,怎知春色幾許?所謂技術的本質就是最大額度地收穫創新,你同意嗎?
原文轉載自「劉悅的技術部落格」 https://v3u.cn/a_id_219