使用智能合约实现自动分账
哈哈笑417
发表于 2023-1-4 14:36:20
231
0
0
文中代码已在 GitHub 上开源。https://github.com/fengluo/fibos-subaccount5 h6 e! v7 a7 U$ W7 n) @. \& r
w) s1 K( w8 i& ]
设计思路4 H$ F$ [$ G9 M9 N, f
! ]' Z. Z$ X {
在 FIBOS 转账是通过 token 合约的extransfer方法来实现的。extransfer方法在执行的时候会给转账方账户和入账方账户发送通知。所以用户给平台方账户转账的时候,平台账户就会收到通知。所以整体业务逻辑如下:
quantity: 10 FO
+ o' r: C4 a2 P @5 \
memo: 内容提供者账户 quantity: 8 FO
8 U) J% V+ I6 G Q
用户账户 -------------------> 平台账户 ----------------> 内容提供者账户' c$ i; T+ a. }( g" N
+ Y2 O- m" k( k4 V6 {1 W4 n
extransfer 2/8 分成 extransfer, P! S6 a4 J; d# F+ B5 [
1.用户给平台方账户转账,memo 中填写内容提供者的账户名。
1 I" P: N& G2 f% b' x& u1 y
2.平台方的账户合约监听 extransfer 方法的通知,然后做出分账计算,给对应内容提供者的账户转账对应金额。
整体逻辑很简单,整个合约代码逻辑差不多用20行就可以写完。* H9 f& _8 i& G7 L
! G# H% f* I- F9 g/ I
编写合约
FIBOS 的智能合约分为 ABI 文件和 JS 合约两部分。ABI 相当于合约接口,JS 合约则是功能实现。本案例目前没有接口设计需求,不过 ABI 文件还是合约不可缺少的部分。所以我们简单创建一下就好。
我们先创建一个 contracts 文件夹,合约文件都会放在这里。然后在此文件夹下,创建 subaccount.abi 文件,内容为:: K- F, y/ a/ M9 L r+ J6 ]$ g
0 |2 p8 {2 g6 J4 s; y: Q! I/ t
{% Y. B3 k- w! c2 j' @
* f; n0 ]) f) X: X* p! @
"version": "eosio::abi/1.0" j/ n6 T5 L6 ^1 Z1 ?
: ^3 ?7 n/ J. B: k
}
_) p$ P- i7 Z H8 F
JS 合约部分也没有太复杂。在 contracts 文件夹下创建 subaccount.js 文件,代码为:
! K% c& h! W [# B- Y( N# J
exports.on_extransfer = (from, to, quantity, memo) => {
4 f/ N. a, L$ I* F' `
// 需要在开头做一些判断
) e# x1 X: @& \
if (to === action.receiver && action.is_account(memo)) {$ G6 C, `6 P5 p2 t4 T4 Y- [
& s, d H8 w C% ?2 B
const num = parseInt(quantity.quantity.split(' ')[0])
// 假设我们约定平台方跟内容提供者是2/8分成。
const subnum = (num * 0.8).toFixed(4);$ J: P* T# {( o$ d1 H
6 Y+ B3 h* i$ Q" G! A c0 N. B
trans.send_inline('eosio.token', 'extransfer', {
8 I+ ^. K9 T0 G+ x @
from: to,6 A# O. e( ~% i+ y1 M4 M# C
; U* B9 g2 n p$ m L
to: memo,8 v4 y4 }) v* t. ]3 D
4 _$ Y$ ?$ z1 |+ }4 z
quantity: {
quantity: `${subnum} ${quantity.quantity.split(' ')[1]}`,6 ^. B8 Q- Z# @
contract: quantity.contract+ C$ r1 ?7 q* k3 e8 I& x
3 y+ F9 d( x: E
},
, `1 F; q6 F5 B+ z) Q2 p" P
memo: 'sub account'
},2 U2 O- D2 H; s( H7 B- b* W/ E
3 d2 J% Y" Q" n3 D9 {
[
+ Q0 ^7 K S' c
{
. m3 ?1 R& s- V2 p. A! D; C: \3 B
// 需要提供合约账户的 active 权限
+ ]' S R$ I- W2 V* p
actor: action.receiver,( ]7 _# \+ b/ F* }
* Q( G; y# X4 m' p% ?6 t
permission: 'active'
}
]);% b( {, @, T! H
( H0 C$ Y8 @3 Q: y$ X" W
}+ w( B9 I9 {; w$ u/ l
}
1 D# I% e8 M5 Y' }7 k# v9 O
合约代码开头我们需要做一些验证。
- k& c' k7 Q- K# {1 ?
1.收款方的账户为合约账户,否则因为下面代码执行给内容提供者转账时,因为转帐方也是合约账号会再次收到通知,造成无限递归,超出最大 send_inline 层数而报错。- M7 Y/ u4 ^# g
& N Y- v3 |1 A
2.我们用 memo 参数来放内容提供者的账户,所以我们需要对此参数校验一下该账户是否存在防止打错。$ d, w+ h: e$ c6 X7 N
% b8 H/ O0 {6 U7 O0 Q Y5 c
合约代码中我们使用 send_inline 调用 eosio.token 合约来执行转帐操作。转帐操作需要对应账户的 active 权限才能执行。为了解决权限滥用问题,FIBOS 定义了一个特殊权限 eosio.code。我们需要在平台合约账户中配置权限,在 active 权限下添加该合约账户的 eosio.code 授权。具体的配置操作会在下面说明。
在 FIBOS TestNet 上注册账号
, W6 a5 Z8 [; ~- {! F0 v& w
为方便测试,我们在测试网 http://testnet.fibos.fo 上注册三个账户。& `$ j/ z4 x2 P# ~& z
& l! [2 X, A% i$ D
用户账号 helloworld11
内容提供者账号 helloworld22
平台合约账号 helloworld33
我们需要记录这三个账号的账户名以及公私钥。以便下面的开发使用。创建一个统一的配置文件来记录这些数据:
3 z; a4 A6 l# z7 N5 g; t( R s
const config = {
// 平台合约账户的客户端配置: q) _% B7 _* o: i; x
- v+ Q0 m& A- E: E
client: {
chainId: '68cee14f598d88d340b50940b6ddfba28c444b46cd5f33201ace82c78896793a',
, F1 N. q* ?; f6 T7 Z9 |' U
httpEndpoint: 'http://testnet.fibos.fo',
0 K: |" B) K8 `# W$ F* d
keyProvider: 'PRIVATE_KEY_OF_helloworld33'8 ?9 g( B% s u- X- ~5 Q
},
// 用户账户的客户端配置
# k6 m: O* W# s* v
callClient:{
" J( }' t6 j. M5 l/ u
chainId: '68cee14f598d88d340b50940b6ddfba28c444b46cd5f33201ace82c78896793a',% h! R. N; k" y! U' T
; N) ]: C: I0 J: J; Q
httpEndpoint: 'http://testnet.fibos.fo',
keyProvider: 'PRIVATE_KEY_OF_helloworld11'8 W% m+ f8 G: g% Y5 D
},
// 平台合约账户信息
contractAccount: {
/ u& g$ H) c% T+ J5 L1 G
name: 'helloworld33',/ }0 X2 {# l5 _$ u* y- z8 @
publicKey: 'PUBLIC_KEY_OF_helloworld33',
/ D2 g! \' L. j( d5 P$ {
privateKey: 'PRIVATE_KEY_OF_helloworld33'
) v4 ?3 u/ K( t1 I) `* \5 c3 t
},
# M1 I4 [/ G9 _# F: {4 h6 [" e
// 用户账户信息
account1: {
^3 D) e( m( {" ^& o6 k7 {8 Q
name: 'helloworld11', p- U4 b- H: {
! t5 f+ P: V& I/ |2 N9 Y
publicKey: 'PUBLIC_KEY_OF_helloworld11',* ^) F; t6 h' D
1 W, ^" x0 v( W. a9 H5 t& o
privateKey: 'PRIVATE_KEY_OF_helloworld11'5 i/ D9 e! C, d6 `$ c1 u/ O
},
// 内容提供者账户信息
; |$ q$ e" M/ v' k
account2: {+ Z- N" J/ ]1 \3 Y; G3 q- S, j
name: 'helloworld22',
publicKey: 'PUBLIC_KEY_OF_helloworld22',
privateKey: 'PRIVATE_KEY_OF_helloworld22'" u7 d7 m" y* s- b8 L
. E" @- s3 S, J$ H/ [( a9 d+ Y' Z
}8 e& b) g! f- [1 a
; O# y5 _6 l9 p" b) j7 P+ n* Z
}& [2 C; X [3 U- ^& K$ e! r4 D" a
module.exports = config5 }5 \8 U1 X6 p" f& G( l, b
配置权限5 B4 [) R# m8 z5 [2 f
在合约代码中,我们调用了 trans.send_inline 函数调用合约 eosio.token 来实现转帐操作,但是转帐操作是需要账户的 active 权限。所以我们需要更新一下合约账户的权限,需要添加调用者的 eosio.code 授权到它的 active 权限。这个调用者自然也是这个合约账户。
const FIBOS = require('fibos.js');% o6 y! l3 I/ ~. b; `8 A
]3 ?: g; J9 e: L! s, Y$ o5 {
const config = require('./config');8 d/ ^- S7 J$ R9 I. \
) {3 h+ |' M0 u
const fibosClient = FIBOS(config.client);
2 t- u( r+ y% x: \ K' h
let ctx = fibosClient.contractSync('eosio');7 ]; ]7 a @% m( x- r
4 F" p C7 p5 p& i. I
var r = ctx.updateauthSync({
! G9 n+ p* |% E$ O+ |% T* ~& q
account: config.contractAccount.name,
permission: 'active',4 f2 }/ D! z' R J, E) X1 q
parent: 'owner',) Q C* Y* }1 ~& o* L( w5 o
auth: {
threshold: 1,8 a; {; h1 U- H2 k+ z$ n
+ d2 `( d y# g+ o+ c1 u, g9 Z
keys: [{; F% y% Z4 N' T' L& q! _' ?
key: config.contractAccount.publicKey,+ ~9 a# W, j) I* W
weight: 16 r9 N. j+ K8 R! v2 Z6 S- j
& Z& I! D- e# N5 W7 q
}],1 ^" V6 G0 i: e: O, z
accounts: [{
9 X) N. X h2 T0 O! o" [
permission: {
7 Q% ~, E! D! o7 o! D! A8 l$ Q( [
// 将调用者账号的 eosio.code 授权添加到它的 active 权限下。5 V6 j5 L- s: l) @6 ~% S
actor: config.contractAccount.name,2 L( M# j" t2 C: P( ]: W; E3 ]) }
. Z# m( y$ b2 N) e
permission: 'eosio.code'
},
& \8 _7 P" y9 K% E6 Q
weight: 1 ~# o% L" Q' ] ~7 Z1 G
}] S% \% G7 m5 G/ @& v
}8 t W( {# T9 U+ i
},{+ H5 J5 j$ s1 o: E) W+ L8 y
authorization: `${config.contractAccount.name}@owner` //更改账户权限需要使用 owner 权限
});/ x: D& U* E/ G. W
) Y/ e# E7 H+ k6 P
console.log(r);
部署合约/ M; a* C C" M5 t. @
: Y& |3 y3 j2 s! b- ]* j
const FIBOS = require('fibos.js');: [# m, _$ P5 ~7 Q
. G# p$ n0 }9 O& d+ h4 u3 k9 _
const config = require('./config');5 j8 o* O3 q1 U
const fibosClient = FIBOS(config.client);% k# j. N3 T! s3 q" \
const fs = require('fs');
// setcode
const jsCode = fs.readTextFile(`${__dirname}/contracts/subaccount.js`);
5 M' _0 [2 c' |; R& r5 F
fibosClient.setcodeSync(config.contractAccount.name, 0, 0, fibosClient.compileCode(jsCode));
5 b; [8 P% Q0 T3 Z! `" o# h
// getcode
const code = fibosClient.getCodeSync(config.contractAccount.name, true);# a3 v: N- B( m3 i
' S! N) I/ J7 t2 n
console.log('code:', code);. V) _$ R _% m' K& {9 O% Z. {
, q6 t1 [2 ?! g2 _7 Q# O6 {( J
// setabi5 x! o' j, b5 ~. H( W6 h5 Q
" ~ E* H1 d! U: W) M: O+ ^2 Y- r
const abi = JSON.parse(fs.readTextFile(`${__dirname}/contracts/subaccount.abi`));
fibosClient.setabiSync(config.contractAccount.name, abi);
9 W2 o% F% B( j& O
转账测试
我们先来写一个脚本 account.js 来查看三个账户的余额。- s, ^2 N3 b% P5 K7 i
const FIBOS = require('fibos.js');
const config = require('./config');' b0 r7 D* I2 |4 Y1 Z' m
const fibosClient = FIBOS(config.callClient);
9 {& B7 p1 x8 J* s0 u& o# q
const account1 = fibosClient.getTableRowsSync(true, 'eosio.token', config.account1.name, 'accounts');) W# G$ ~( T, D/ I
) ^4 m* [' V8 F+ u- Q
console.log(config.account1.name);
console.log(account1);& i/ c$ h8 `5 k7 V( N) V+ U
% _1 G/ a$ k- [! |
const account2 = fibosClient.getTableRowsSync(true, 'eosio.token', config.account2.name, 'accounts');2 w5 `- L: O% t. Q; U" Z
. M. L; q8 S9 B
console.log(config.account2.name);
# I$ R6 [6 v/ e9 d
console.log(account2);
9 E, l# P( w/ J. |$ {# X
const contractAccount = fibosClient.getTableRowsSync(true, 'eosio.token', config.contractAccount.name, 'accounts');
console.log(config.contractAccount.name);' c7 ]8 q/ B/ I
console.log(contractAccount);
2 M& g7 ?9 ?) U/ T- x8 X
执行 fibos account.js 来查看三个账户信息。 目前我们的账户还没有 FO,所以大致情况是这样的:
% t* A: P7 Y' P" h
用户账户:helloworld11 金额:0.0000 FO内容提供者账户:helloworld22 金额:0.0000 FO平台合约账户:helloworld33 金额:0.0000 FO4 W& a) n6 M M
测试网会自动给每个账户发放10 EOS 的通证用以测试使用。账户中还并没有 FO 通证。所以我们再来写一个兑换脚本,用1 EOS 换一点 FO 通证。4 `4 V5 K5 l0 T3 j# I# z) Q
7 X8 V- Q8 d1 P: e. \% }% a
const FIBOS = require('fibos.js');
* G5 {$ H$ h% {) [" M
const config = require('./config');
const fibosClient = FIBOS(config.callClient);
! i: i7 {; Z* D+ q& k
let ctx = fibosClient.contractSync('eosio.token');
* {$ C, d" G* S1 Z
const r = ctx.exchangeSync(5 h9 Y0 E/ X3 U
config.account1.name,
'1.0000 EOS@eosio',& s' B" t( d( \% r/ F$ i5 ]4 t
; u4 j2 e. T: W b9 G" A% |
'0.0000 FO@eosio',
3 k. O. z% v- ~0 J- x( C
'exchange FO to EOS',. |/ F$ R6 Z* _7 B) O. [2 q6 r
: K# _ F3 u t h9 [6 `* o1 D
{
0 X$ a9 P3 N" c5 C0 a, B7 c
authorization: config.account1.name
}( Y" P2 x+ S: H9 V
);2 e% M5 u4 n/ P# c7 w
! L- ` B4 @3 q% I- A2 t
console.log(r)
再次执行 fibos account.js 来查看账户信息。目前我们的账户金额大致是这样的:, x( G& g0 h6 u
用户账户:helloworld11 金额:146.4245 FO内容提供者账户:helloworld22 金额:0.0000 FO平台合约账户:helloworld33 金额:0.0000 FO( E* w* j& s2 L8 r$ Z, r6 f4 ^- w, m, t
下面写个脚本 transfer.js 来执行转帐操作。
9 W+ f# k( S0 }4 j! }/ D/ C2 B
const FIBOS = require('fibos.js');/ G/ A$ e9 P5 l* U# \4 q
const config = require('./config');2 u. ]: \$ A& D9 a/ {9 S% y
const fibosClient = FIBOS(config.callClient);8 Y' m: F; G; }# Z5 x
y8 l9 ~9 r; }. G9 }4 Q7 k
let ctx = fibosClient.contractSync('eosio.token');
const r = ctx.extransferSync(, P8 H7 \, ~* Z0 E. ]2 q1 B
config.account1.name, // 用户账户
5 O! h m/ M" G6 Z- n0 `
config.contractAccount.name, // 平台合约账户5 K' e9 T* K1 b, [* F7 @) i0 V
'10.0000 FO@eosio', // 转帐金额+ L$ I: T. k% } v U V2 ~
config.account2.name, // 附言填写内容提供者的账户名,平台合约会给它分账) Z6 M; s& E) p/ C% c# i- [5 d# q8 |
% w( N" H7 C6 c% m" ` @# S
{9 w; K4 X0 e# n1 `* d
authorization: config.account1.name //提供用户账户的授权
( n( e( q6 ?0 A; O' x0 c, n
}
)& ~* }# S; L( h# h u
, Z' x. p2 B% |% [
console.log(r)% n% `$ G2 @- c9 X1 }
% Q- M1 y+ _+ T9 M
我们要从用户账户 account1 给平台合约账户 account3 转帐 10 FO。memo 参数为要分成的内容提供者账户 account2。根据合约中定的2/8分成,平台合约账户 account3 将会分得2 FO,而内容提供者账户 account2 将会获得8 FO。 } h6 f) s# v: p' s. s
使用命令 fibos transfer.js 执行该脚本完成转帐操作。2 i6 [/ s8 }( z* J6 N6 B7 e4 c
0 a9 R7 j+ J6 o, P" V
下面我们再来看一下目前三个账户情况。执行命令 fibos account.js。三个账户金额大致如下。$ d& v. A+ e4 r! f9 S6 G
4 A# ~& T" D! Q/ }' \3 g$ z6 D
用户账户:helloworld11 金额:136.4245 FO内容提供者账户:helloworld22 金额:8.0000 FO平台合约账户:helloworld33 金额:2.0000 FO
: c) A+ z) r6 \# s! A/ `% ~
结果显示,分账账户和平台合约账户如预期那样获得8 FO 和2 FO。 g) U( e- s) K2 t4 ?/ M: \
; l P* t% ~ Z* w3 T& z* @
综上,我们成功使用了智能合约实现了自动分账。平台方还可以继续根据自己业务需要定制自己的合约。) k) X' f7 f3 R+ f
1 H8 L, ?0 C8 Z( e' v! G! H
文中的代码请参考:https://github.com/fengluo/fibos-subaccount
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
成为第一个吐槽的人