使用智能合约实现自动分账
哈哈笑417
发表于 2023-1-4 14:36:20
117
0
0
文中代码已在 GitHub 上开源。https://github.com/fengluo/fibos-subaccount. M+ G2 |! |9 ]9 L( ~4 ^
设计思路
在 FIBOS 转账是通过 token 合约的extransfer方法来实现的。extransfer方法在执行的时候会给转账方账户和入账方账户发送通知。所以用户给平台方账户转账的时候,平台账户就会收到通知。所以整体业务逻辑如下:/ g9 d% V: }; O r: d
- n5 B4 T# |( \
quantity: 10 FO6 q0 H6 y+ k! [$ I
5 O6 y3 H$ X8 A7 o- J) ?5 f2 s
memo: 内容提供者账户 quantity: 8 FO3 q5 h) j* K5 X* \0 q2 s' S& x
* N; R* o/ |9 `2 E1 i
用户账户 -------------------> 平台账户 ----------------> 内容提供者账户
5 O. m8 \+ P6 b' q$ M7 W8 Z3 ~, j b; J
extransfer 2/8 分成 extransfer
) P; V, T e: }
1.用户给平台方账户转账,memo 中填写内容提供者的账户名。; E* N7 d2 a/ |$ |% V) M$ u
1 d8 K t( A+ W4 D+ p5 y
2.平台方的账户合约监听 extransfer 方法的通知,然后做出分账计算,给对应内容提供者的账户转账对应金额。' C; O1 j- c! _" M7 b* ?: j$ m0 I- D
5 `3 e, j s+ c8 ]
整体逻辑很简单,整个合约代码逻辑差不多用20行就可以写完。* V9 R' N& D s5 ?: [7 G7 U
编写合约1 Z+ C! n4 O# `" s/ f
FIBOS 的智能合约分为 ABI 文件和 JS 合约两部分。ABI 相当于合约接口,JS 合约则是功能实现。本案例目前没有接口设计需求,不过 ABI 文件还是合约不可缺少的部分。所以我们简单创建一下就好。
我们先创建一个 contracts 文件夹,合约文件都会放在这里。然后在此文件夹下,创建 subaccount.abi 文件,内容为:
{
" g0 k, Q3 i. b- p8 M# O/ L* F
"version": "eosio::abi/1.0"+ W. f5 _/ [ J) |5 u+ g' O" p
}1 w3 P% V6 ~' p4 ^- Q- F
JS 合约部分也没有太复杂。在 contracts 文件夹下创建 subaccount.js 文件,代码为:
/ i( O0 @% m; B& Z3 h5 F
exports.on_extransfer = (from, to, quantity, memo) => {
% d$ k' r0 j8 v. v
// 需要在开头做一些判断, C: @- k! H& M2 }: z5 v' J& V
if (to === action.receiver && action.is_account(memo)) {4 r! Y" {$ W/ z7 {
const num = parseInt(quantity.quantity.split(' ')[0])
// 假设我们约定平台方跟内容提供者是2/8分成。
8 Q& M+ X F2 f j2 ?
const subnum = (num * 0.8).toFixed(4);- {$ S# u9 Z( h
trans.send_inline('eosio.token', 'extransfer', {
from: to,3 v9 R6 w0 a4 }2 D2 s; M: s
0 B: s1 f" [6 q# ^ I
to: memo,
2 W3 P' J+ F" e: ]- I8 L& y4 W
quantity: {$ a- i0 Q" |) m5 n. g L% i
6 F8 f, K5 }4 B4 W
quantity: `${subnum} ${quantity.quantity.split(' ')[1]}`,
9 q+ ]% ]' _# x/ n" p
contract: quantity.contract9 H. V0 p, G. C' w" R; r0 v
},
# Y8 N/ S% b' c. o7 `
memo: 'sub account'! w' s# H I) `
7 i* y: O5 |* b) i* U% q
},
[* i- P4 F' _, _# j7 L* _
( c& f1 `; z( \/ y
{3 b/ q9 r' p# ~5 t7 B7 C% N4 V$ Q7 U
// 需要提供合约账户的 active 权限
{/ a2 Q9 i' Z) u [
actor: action.receiver," I; O& K m! p# Y8 k; w! a4 S" i
& o6 \2 r X7 X: B, z/ O
permission: 'active'
9 K' e2 h5 u c; o' E" n
}
]);
}
5 E7 {# ~# C& B; d Z
}7 N, n6 ]6 q% S; ^# M( p
合约代码开头我们需要做一些验证。" e. Q6 t) V6 H
1.收款方的账户为合约账户,否则因为下面代码执行给内容提供者转账时,因为转帐方也是合约账号会再次收到通知,造成无限递归,超出最大 send_inline 层数而报错。 A- ] \. S8 G
/ l% y# X3 t& A; A2 d/ r' Y
2.我们用 memo 参数来放内容提供者的账户,所以我们需要对此参数校验一下该账户是否存在防止打错。, a: g+ N0 N. x2 t: H1 X
合约代码中我们使用 send_inline 调用 eosio.token 合约来执行转帐操作。转帐操作需要对应账户的 active 权限才能执行。为了解决权限滥用问题,FIBOS 定义了一个特殊权限 eosio.code。我们需要在平台合约账户中配置权限,在 active 权限下添加该合约账户的 eosio.code 授权。具体的配置操作会在下面说明。
在 FIBOS TestNet 上注册账号
为方便测试,我们在测试网 http://testnet.fibos.fo 上注册三个账户。+ \' T7 d( Y+ O, K1 I
) d. g7 ]+ m+ ?! d |1 j5 B
用户账号 helloworld11" P0 |/ c+ r2 A6 ]. _
内容提供者账号 helloworld225 y; h# r- R) A* T; T
平台合约账号 helloworld338 I. V. R% k2 a* }( o: q* F
5 A: X: W# L8 o' V
我们需要记录这三个账号的账户名以及公私钥。以便下面的开发使用。创建一个统一的配置文件来记录这些数据:
const config = {
; O4 _2 R8 E" _8 G1 R+ ^! K
// 平台合约账户的客户端配置' _" D. Y: W2 h4 p8 I
K) w; }7 @3 h
client: {
chainId: '68cee14f598d88d340b50940b6ddfba28c444b46cd5f33201ace82c78896793a',; l8 r5 [# R8 s, J
' M9 @! l: u/ Y: j2 L
httpEndpoint: 'http://testnet.fibos.fo',& H% J$ l0 V" S, E2 s" m$ I5 g
5 a. W$ ]: D0 M
keyProvider: 'PRIVATE_KEY_OF_helloworld33'
},
// 用户账户的客户端配置
' v( M0 z! S5 \6 \0 K
callClient:{/ W( u/ o' D. G% _- c) \% Q, q; x
* H( B2 m% K% j
chainId: '68cee14f598d88d340b50940b6ddfba28c444b46cd5f33201ace82c78896793a',3 G% @5 ?+ H9 [/ h& g) O* q& d
& l3 p5 N/ u6 J1 s* M6 K" Z
httpEndpoint: 'http://testnet.fibos.fo',
keyProvider: 'PRIVATE_KEY_OF_helloworld11'
$ \8 _6 C! O0 w0 i
},3 ^* ]8 ], `7 d, Y% d: ?( C; u% I
// 平台合约账户信息8 `9 x) J. S0 _
) w2 P% J$ D) r2 ~0 z
contractAccount: {, d' ?9 b' T; M% {' l6 P% [' i
* V g- t; f( e$ D
name: 'helloworld33',9 S5 u( ~3 ]" X$ U4 w
/ Y5 M; ]/ W5 |9 w! A
publicKey: 'PUBLIC_KEY_OF_helloworld33',
`& p+ i6 W1 j
privateKey: 'PRIVATE_KEY_OF_helloworld33'& _- \+ k7 z9 v+ H! S+ u
+ _, W; F. h# o, q
},5 g# Q' z7 C3 G" f7 x
// 用户账户信息* ?4 T: k" h8 D: @8 b6 O" V7 c" {. q
6 S4 ?7 f1 h' E6 ?8 l! c
account1: {4 H/ W5 V" ~ o4 g
name: 'helloworld11',
publicKey: 'PUBLIC_KEY_OF_helloworld11',
privateKey: 'PRIVATE_KEY_OF_helloworld11'3 `0 f. c. Z" P% i2 W
},
4 ~3 t- ~7 E- d; y. @
// 内容提供者账户信息
, ~5 j; }2 j" G% L
account2: {
. `5 l0 q/ U" u: d
name: 'helloworld22',
! O# |% r, P$ D/ i
publicKey: 'PUBLIC_KEY_OF_helloworld22',: x9 t9 ` G, A6 i6 W6 A
privateKey: 'PRIVATE_KEY_OF_helloworld22'
}: b' S9 r) a" q7 @/ R+ P+ ]+ m
2 M/ ?& m- r$ s( u
}
7 [. T' ^6 t3 m- j7 K: L v' Q
module.exports = config
+ t1 E) d# P( @3 ^ P) K2 s7 k
配置权限 o* }% y* q* g% H% y8 Q
7 a/ I* O2 E$ d
在合约代码中,我们调用了 trans.send_inline 函数调用合约 eosio.token 来实现转帐操作,但是转帐操作是需要账户的 active 权限。所以我们需要更新一下合约账户的权限,需要添加调用者的 eosio.code 授权到它的 active 权限。这个调用者自然也是这个合约账户。
const FIBOS = require('fibos.js');- Y& `7 j* _/ K. L* a
const config = require('./config');& U7 c1 O1 T) @8 W4 ]* H( I2 s( }
const fibosClient = FIBOS(config.client);# @) h) u: J# z2 }1 f
let ctx = fibosClient.contractSync('eosio');, g: F2 q' }2 D* }& M; _
var r = ctx.updateauthSync({
9 ~* R, _' D3 _9 l
account: config.contractAccount.name,
0 U g* F. S! j
permission: 'active',/ e% a- h- l2 C1 t6 e
parent: 'owner',
8 p8 |; l* T% I4 o$ v/ v: R- u
auth: {
2 Q# j2 E/ G B x" R+ g
threshold: 1,$ e5 P0 A( E9 S+ } ?/ [
5 @' z9 R6 R5 T
keys: [{
" i8 h6 S/ E0 [2 A
key: config.contractAccount.publicKey,
weight: 1
, x9 S# ~* v4 v
}],
accounts: [{1 x) G4 `9 c2 g( R; j, ^; w9 h
% [3 D7 _$ z, D7 Q) V9 L
permission: {3 S4 d; \- V7 @4 m2 D/ x5 g1 I! i
// 将调用者账号的 eosio.code 授权添加到它的 active 权限下。
5 p. \( f8 ]! ^+ @0 @, W c; G
actor: config.contractAccount.name,2 p( a/ K. c. B; \# _
' x' t* o# i# m ?" A
permission: 'eosio.code'
},. h7 e' u7 Q. [+ n
1 w7 i1 g* |( n* w
weight: 1
}]
}
2 n& k# J, u+ n0 J
},{
authorization: `${config.contractAccount.name}@owner` //更改账户权限需要使用 owner 权限1 d' D! L3 f. l7 \
- ?) D+ G- z T! y ~( S2 f
});
console.log(r); }9 E7 u0 z" g1 u l, \
; i% J0 h: [" v, X$ o- A
部署合约
* u( v2 T- L5 g' y
const FIBOS = require('fibos.js');
/ k/ v: ?& t4 [
const config = require('./config');
6 `" g1 K4 i( }
const fibosClient = FIBOS(config.client);$ E% v9 k x. u c6 ^) ]0 S9 h
6 {3 L. Z, h7 J' s( J) |
const fs = require('fs');
// setcode. o5 y4 l- E) e3 F; o
4 _3 J8 S$ z- b
const jsCode = fs.readTextFile(`${__dirname}/contracts/subaccount.js`);) E* H* h( G% ~: l
fibosClient.setcodeSync(config.contractAccount.name, 0, 0, fibosClient.compileCode(jsCode));
// getcode
const code = fibosClient.getCodeSync(config.contractAccount.name, true);9 m) D, Q8 q0 V1 ]9 T! j5 d6 J# o
console.log('code:', code);7 V# E0 w. x+ w4 K
// setabi y' s2 I9 \+ t0 @. |, g
const abi = JSON.parse(fs.readTextFile(`${__dirname}/contracts/subaccount.abi`));
3 d1 k/ s( S: t2 \( j( ]; |8 a! o
fibosClient.setabiSync(config.contractAccount.name, abi);3 Y6 b. ?! y W' ~$ j0 X1 `- S
转账测试
我们先来写一个脚本 account.js 来查看三个账户的余额。
const FIBOS = require('fibos.js');
. R% h5 B6 t. }4 g
const config = require('./config');6 g Y, z' ]4 I; D5 Y6 j0 ? u
const fibosClient = FIBOS(config.callClient);9 H" Q3 w6 ^" O" K7 }
const account1 = fibosClient.getTableRowsSync(true, 'eosio.token', config.account1.name, 'accounts');, L# @/ D7 @; M* Q. a
console.log(config.account1.name);4 D% {- a$ k: d0 V! d z: a* p; w
+ z9 l( }* Q4 b, o9 Y9 ]' j
console.log(account1);; y8 e; a5 w* u1 E
9 t# M6 ~. a7 r# z# q/ m
const account2 = fibosClient.getTableRowsSync(true, 'eosio.token', config.account2.name, 'accounts');
# ?9 H- X6 d& T l. I9 A+ M2 ^
console.log(config.account2.name);
console.log(account2);
: s& v+ V0 c& L0 x4 d5 U
const contractAccount = fibosClient.getTableRowsSync(true, 'eosio.token', config.contractAccount.name, 'accounts');& M) q5 l4 M7 n. F( }/ `
) s$ S' N3 d, g/ b
console.log(config.contractAccount.name);+ w/ u; Q& N7 v% A6 b
console.log(contractAccount);
, f$ r3 ?* [9 {8 d
执行 fibos account.js 来查看三个账户信息。 目前我们的账户还没有 FO,所以大致情况是这样的:1 V9 c2 u* Z2 A1 f7 a" g+ ^0 N
用户账户:helloworld11 金额:0.0000 FO内容提供者账户:helloworld22 金额:0.0000 FO平台合约账户:helloworld33 金额:0.0000 FO) m* ^# Q; M3 ~, r
! u% ]# U3 S; A2 b
测试网会自动给每个账户发放10 EOS 的通证用以测试使用。账户中还并没有 FO 通证。所以我们再来写一个兑换脚本,用1 EOS 换一点 FO 通证。
const FIBOS = require('fibos.js');1 _. m$ M4 `. h& \. A
const config = require('./config');5 w% J% T" g( l
const fibosClient = FIBOS(config.callClient);
- f% L' {1 c% M. f, h+ `
let ctx = fibosClient.contractSync('eosio.token');% t7 Q! x' L5 s7 e' J
& P/ |1 h$ V3 L- h) D/ Y/ a3 s, T
const r = ctx.exchangeSync($ t9 g1 f1 r' W. l
config.account1.name,
'1.0000 EOS@eosio',! @+ j7 n+ W+ W/ R( S: Z
3 |2 F% l' l, d$ Q/ G
'0.0000 FO@eosio',
'exchange FO to EOS',5 K+ q4 l- n- {: O
6 F8 J1 f) e* C+ ^ J
{8 H. o9 ]( }* R! L
H# c$ a. [ v- P' s* Q
authorization: config.account1.name
1 M* }* s4 h7 B* a* N
}
);# Z n# D4 a: }0 @9 F. i
console.log(r), I! A% g& {, f% `
1 t) I6 j0 ^! m4 q% d) p% m6 S3 k
再次执行 fibos account.js 来查看账户信息。目前我们的账户金额大致是这样的:
用户账户:helloworld11 金额:146.4245 FO内容提供者账户:helloworld22 金额:0.0000 FO平台合约账户:helloworld33 金额:0.0000 FO
: h6 U( Q& C7 A0 H" _
下面写个脚本 transfer.js 来执行转帐操作。
const FIBOS = require('fibos.js');# y* o1 N) `, N2 t$ J, E1 Y9 r
" D# A. u$ L6 ~' ~& r
const config = require('./config');
const fibosClient = FIBOS(config.callClient);1 \- e' n' a3 |9 n
3 w& n% K: W- n' D4 l
let ctx = fibosClient.contractSync('eosio.token');
const r = ctx.extransferSync() a9 N4 ?+ d% S C3 h
config.account1.name, // 用户账户7 y' @# q( V$ X& j8 [
9 ?$ R+ j+ o/ B! l3 j/ [6 u
config.contractAccount.name, // 平台合约账户! j2 O& h# [2 b* g* i0 C4 z* `
+ G3 @% h) E) f4 e7 S# ?9 e
'10.0000 FO@eosio', // 转帐金额
3 Q% D' L' D9 F9 c
config.account2.name, // 附言填写内容提供者的账户名,平台合约会给它分账$ ~- I, T: ^: E; v
3 s' \' S" w" Z5 Y8 R+ P
{2 C" n' Z: Z8 E
8 S) X: _6 Y; I- D" X
authorization: config.account1.name //提供用户账户的授权7 u8 a/ C' f: p. N
}
)3 @2 c: y, m/ }% q
console.log(r)
: Y% c* D7 N/ \$ j
我们要从用户账户 account1 给平台合约账户 account3 转帐 10 FO。memo 参数为要分成的内容提供者账户 account2。根据合约中定的2/8分成,平台合约账户 account3 将会分得2 FO,而内容提供者账户 account2 将会获得8 FO。- S" r5 X9 M! y9 t3 o
使用命令 fibos transfer.js 执行该脚本完成转帐操作。$ Z0 ]6 e3 I. i5 j. L2 l
7 t$ }0 B! s( H* m7 Y: t" z3 U
下面我们再来看一下目前三个账户情况。执行命令 fibos account.js。三个账户金额大致如下。3 u' G& r. |0 X9 ~* ?
6 Z* H, z+ T2 f) t. G( }7 r8 S
用户账户:helloworld11 金额:136.4245 FO内容提供者账户:helloworld22 金额:8.0000 FO平台合约账户:helloworld33 金额:2.0000 FO
结果显示,分账账户和平台合约账户如预期那样获得8 FO 和2 FO。
: x/ U) v( q' z6 G" f
综上,我们成功使用了智能合约实现了自动分账。平台方还可以继续根据自己业务需要定制自己的合约。- v6 F* @+ @ [* T1 h% m9 @! j
$ }* ^+ g' |3 w; a: ]9 S
文中的代码请参考:https://github.com/fengluo/fibos-subaccount
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
成为第一个吐槽的人