使用智能合约实现自动分账
哈哈笑417
发表于 2023-1-4 14:36:20
116
0
0
文中代码已在 GitHub 上开源。https://github.com/fengluo/fibos-subaccount7 G2 S, @ n3 ~
设计思路
在 FIBOS 转账是通过 token 合约的extransfer方法来实现的。extransfer方法在执行的时候会给转账方账户和入账方账户发送通知。所以用户给平台方账户转账的时候,平台账户就会收到通知。所以整体业务逻辑如下:
quantity: 10 FO. k& e. J# x! A; c+ @
8 W$ \4 ]) h- E. S# K
memo: 内容提供者账户 quantity: 8 FO: b* O c5 u7 C% f! r
用户账户 -------------------> 平台账户 ----------------> 内容提供者账户
extransfer 2/8 分成 extransfer
1.用户给平台方账户转账,memo 中填写内容提供者的账户名。
2.平台方的账户合约监听 extransfer 方法的通知,然后做出分账计算,给对应内容提供者的账户转账对应金额。$ d2 E6 O' u# P1 ?1 q
3 T; x, ?( `/ g3 I. h/ P
整体逻辑很简单,整个合约代码逻辑差不多用20行就可以写完。& C& q- I" P7 I1 v6 c
# p6 Y, @6 @" E; \2 ~9 e
编写合约
FIBOS 的智能合约分为 ABI 文件和 JS 合约两部分。ABI 相当于合约接口,JS 合约则是功能实现。本案例目前没有接口设计需求,不过 ABI 文件还是合约不可缺少的部分。所以我们简单创建一下就好。+ F0 ~& x3 A* H% c% o
5 M% g2 |$ J1 v. O M
我们先创建一个 contracts 文件夹,合约文件都会放在这里。然后在此文件夹下,创建 subaccount.abi 文件,内容为:2 T1 @& S3 S7 q' ]( q$ w8 c
{* I! B& J" ?8 g$ U
" r6 ?- e3 A& L# k: V! l$ o5 |8 {/ i% }
"version": "eosio::abi/1.0"6 B! T# _2 h! }! a/ S+ Y
; n: ^( H( W: H4 T& U
}! v7 K. X2 ^0 O( `- w" n
& }4 o, y' Q3 O; e4 F9 y
JS 合约部分也没有太复杂。在 contracts 文件夹下创建 subaccount.js 文件,代码为:& r: C2 S T1 E$ {1 l7 ?$ }
exports.on_extransfer = (from, to, quantity, memo) => {
// 需要在开头做一些判断
if (to === action.receiver && action.is_account(memo)) {1 @( D6 m) E5 U/ ~5 X9 c7 y8 l
% m8 [. D4 I; C, V4 o( K4 e) P
const num = parseInt(quantity.quantity.split(' ')[0])
// 假设我们约定平台方跟内容提供者是2/8分成。
, x* Q: y) ?! X* N- N
const subnum = (num * 0.8).toFixed(4);
+ i5 b- z0 g7 d8 Z& @' ]% N* A
trans.send_inline('eosio.token', 'extransfer', {2 `& E: J# {$ i4 F9 [1 Q8 p
from: to,
to: memo,( u, a+ ?9 G1 \" h
quantity: {
quantity: `${subnum} ${quantity.quantity.split(' ')[1]}`,5 d: P4 e& _+ H' S' }
contract: quantity.contract8 a0 {1 S6 b" O: B
},0 U( J4 r2 k/ n; g/ g
memo: 'sub account'- q6 x- a/ D# D6 L2 ?5 x) a
- o9 J. C3 M1 r. u/ k) O4 g2 R8 @& S
},/ B3 k5 W6 R: P8 h! {5 A
) e+ \2 k( Y4 n% T: I; s
[
9 i5 ~3 [: u& f
{
// 需要提供合约账户的 active 权限
actor: action.receiver,) C* _% C/ p, B: m9 v
* q; o, G$ s2 b- }2 m- d3 ~
permission: 'active'& v& c- c1 P* r% S
}
# W2 v6 U8 i. y1 U& w
]);
# U% U$ K. A2 V1 Z3 F& v; j& n* I
}
}& b/ f- r- A o
合约代码开头我们需要做一些验证。
) z; V2 G, ?( |, t/ V
1.收款方的账户为合约账户,否则因为下面代码执行给内容提供者转账时,因为转帐方也是合约账号会再次收到通知,造成无限递归,超出最大 send_inline 层数而报错。9 @: a' n0 S- U. ~9 \4 n" L H& Y
; l0 v& i6 N" I! \& N
2.我们用 memo 参数来放内容提供者的账户,所以我们需要对此参数校验一下该账户是否存在防止打错。. r: S, Q/ k) z$ M
0 |' \9 G9 F' Y" x6 }
合约代码中我们使用 send_inline 调用 eosio.token 合约来执行转帐操作。转帐操作需要对应账户的 active 权限才能执行。为了解决权限滥用问题,FIBOS 定义了一个特殊权限 eosio.code。我们需要在平台合约账户中配置权限,在 active 权限下添加该合约账户的 eosio.code 授权。具体的配置操作会在下面说明。) D# d' X2 p: u7 P$ X0 y; z
' H0 L% I+ i9 f) I3 U# ~2 {
在 FIBOS TestNet 上注册账号) A9 f) A2 y7 H/ z: I; h$ Y
为方便测试,我们在测试网 http://testnet.fibos.fo 上注册三个账户。
5 j* _; A" I, n
用户账号 helloworld11
$ M; |' f% v. N( o8 M
内容提供者账号 helloworld22% ]' o9 I8 I' G: z d- N. c- O
; U+ J M9 `. I, W( y5 W
平台合约账号 helloworld33; e6 m' [) h3 Z8 _- h T5 Z
) H! H7 d1 r. O# v
我们需要记录这三个账号的账户名以及公私钥。以便下面的开发使用。创建一个统一的配置文件来记录这些数据:$ p" p' u, o5 h/ m- R" q
const config = {/ i6 x6 n7 J5 v
// 平台合约账户的客户端配置$ Q/ ? W( P% `3 ^! [: k6 \
9 Z* R! R! f, s8 i8 w
client: {6 m' z5 L3 X* T0 V! _
chainId: '68cee14f598d88d340b50940b6ddfba28c444b46cd5f33201ace82c78896793a',' q% f% [7 c0 s! h, O! m0 q
httpEndpoint: 'http://testnet.fibos.fo',/ n0 u0 R6 _! l+ j
keyProvider: 'PRIVATE_KEY_OF_helloworld33'
# f' H+ K$ |; T1 [" [2 T
},
// 用户账户的客户端配置' D1 ?3 Y5 b# ~+ u8 T: ?' G. V
callClient:{: ^0 h9 M z& y
chainId: '68cee14f598d88d340b50940b6ddfba28c444b46cd5f33201ace82c78896793a',, _/ u% G- Y* j8 S1 L3 X5 n- U2 J
8 a3 f* X/ S% |$ ?
httpEndpoint: 'http://testnet.fibos.fo',4 J# {" G6 j( U: g$ Z) q4 M! Y
keyProvider: 'PRIVATE_KEY_OF_helloworld11'9 ~0 I1 D m1 N, R: ~* v" |1 t
/ D- n" \- N7 q9 M: X
},4 ^ j6 F, H5 {, |. l( ~6 ]0 z5 l
. X& r& S8 X! v1 _
// 平台合约账户信息' j1 ], [" c+ C, e- |0 u2 q" s
! O& I! Y7 A0 H! p0 R) I7 k
contractAccount: {
; e. M/ t- J9 d! I6 v
name: 'helloworld33',
publicKey: 'PUBLIC_KEY_OF_helloworld33',
privateKey: 'PRIVATE_KEY_OF_helloworld33'" Y/ I) c( K! o! Q0 ^! H1 g
},
& u$ a8 u( k5 G* G; w: j; ]
// 用户账户信息
4 ?2 ^7 n3 _+ l: U
account1: {" M6 v6 l( J8 O7 j
8 t8 o" ]* P: c( b5 u1 a" G, S9 n
name: 'helloworld11',
* A; W. h9 ~" l) ]$ k! L
publicKey: 'PUBLIC_KEY_OF_helloworld11',
- `" y3 U! m9 x- Y3 H
privateKey: 'PRIVATE_KEY_OF_helloworld11'
},/ \; A* W0 r6 `6 f5 Z% {
: }5 Y+ s! B) X* d) d
// 内容提供者账户信息
account2: {
name: 'helloworld22',
. c4 @: ^, q9 @& r5 P+ m9 \9 {
publicKey: 'PUBLIC_KEY_OF_helloworld22',2 n0 \4 d0 X2 O/ o
privateKey: 'PRIVATE_KEY_OF_helloworld22'
}
}/ `; r' t5 V. ~& w C6 F! g
module.exports = config; j4 O# W4 N$ {; j8 T3 I# S: u# `
配置权限
. [* D5 h9 N" o; k0 m
在合约代码中,我们调用了 trans.send_inline 函数调用合约 eosio.token 来实现转帐操作,但是转帐操作是需要账户的 active 权限。所以我们需要更新一下合约账户的权限,需要添加调用者的 eosio.code 授权到它的 active 权限。这个调用者自然也是这个合约账户。 X7 l0 [" j" M1 O( C& s3 e7 X% H
const FIBOS = require('fibos.js');/ O7 k4 |- Z2 z1 c/ N+ n, H
6 V3 f: X" `! |% q) |) t2 ]
const config = require('./config');0 x4 ], j* T8 p0 I3 a
2 [( a- l6 N5 G1 Y. ^4 B
const fibosClient = FIBOS(config.client);& }( I& s: T5 @/ W) Z( k/ c. j7 e, Y
1 I! F% p* f' g+ R; X5 T- R ?
let ctx = fibosClient.contractSync('eosio');
var r = ctx.updateauthSync({
# a8 K1 v% D/ }: @9 |0 p* T
account: config.contractAccount.name,+ S7 c4 `6 `, Z4 {) P
permission: 'active',
parent: 'owner',
3 x" t& b1 u1 ~0 R$ ]; F: o
auth: {$ G& P" O1 g- m9 e* z. e N" T
threshold: 1,
' Y7 n# A/ j; u! c
keys: [{, q9 C3 K) S, `- a5 O
key: config.contractAccount.publicKey,
+ Y: t) G: v+ `* a1 A- }0 G2 F
weight: 1
* W% a# U4 i: |2 P
}],! @6 \" Z- ^! X" a: W4 c
accounts: [{
% A5 A' y0 A, g: x ~
permission: {
// 将调用者账号的 eosio.code 授权添加到它的 active 权限下。, H, N# x7 K* c5 ^0 p9 X
2 D1 ~: a# Y% v3 k7 ?
actor: config.contractAccount.name,
permission: 'eosio.code'
},
weight: 1
( \/ q; d' c8 @) ]
}]' ]0 q; P$ h- c9 U4 _ m
}+ d/ x# M$ O1 ?" G; H7 M" A
},{
a& H1 n7 @# V) q
authorization: `${config.contractAccount.name}@owner` //更改账户权限需要使用 owner 权限' k1 t6 R, M Y$ `# S* B
});
console.log(r);9 O/ w0 e3 w9 \$ m' q5 i o& J+ V
; b/ C, N: Z4 }2 _' {
部署合约' s2 H P" S" S; m( h
const FIBOS = require('fibos.js');
const config = require('./config');8 \& k' L) I. V8 F! |* L4 q5 a
* ]- Y- v/ n5 E0 s
const fibosClient = FIBOS(config.client);
! t& F9 V" r# s9 X+ `0 `
const fs = require('fs');. L9 h% d8 E$ r% P6 Z5 k
( ]8 Z3 x$ k, Z; _8 n
// setcode& F: `5 s* o! Y1 T4 D; d
% w" T: v$ M3 w( [
const jsCode = fs.readTextFile(`${__dirname}/contracts/subaccount.js`);
fibosClient.setcodeSync(config.contractAccount.name, 0, 0, fibosClient.compileCode(jsCode));9 x5 w: b( P: |: D4 y
! I0 h! C, |/ @6 L- [. F
// getcode
5 E) z6 d- @5 A: ~' ?
const code = fibosClient.getCodeSync(config.contractAccount.name, true);2 U$ A0 `* L0 }! x7 {( n
console.log('code:', code);
// setabi1 V0 f$ l/ d! l" h- k$ G
const abi = JSON.parse(fs.readTextFile(`${__dirname}/contracts/subaccount.abi`));6 }# O8 Y. V y$ {' ]2 g, {
4 L+ _% ~/ n5 V) D
fibosClient.setabiSync(config.contractAccount.name, abi);( Z9 ^5 z, L& W+ ^3 V
转账测试) G/ P Y7 M5 W" P0 A9 C% K
我们先来写一个脚本 account.js 来查看三个账户的余额。
% N, Y( {6 U+ p1 ^" Y; x
const FIBOS = require('fibos.js');
& @3 c% L& @! u2 N% y
const config = require('./config');/ u0 X4 Q2 v1 W; @4 \) h/ E4 |; x
const fibosClient = FIBOS(config.callClient);$ D# y+ r6 m3 Q% K( l Y q
const account1 = fibosClient.getTableRowsSync(true, 'eosio.token', config.account1.name, 'accounts');
console.log(config.account1.name);
% j, D2 }$ Y$ P& g
console.log(account1);+ ]/ T' o: W7 q8 E) J
& U5 r. b) w: g' N* Q
const account2 = fibosClient.getTableRowsSync(true, 'eosio.token', config.account2.name, 'accounts');
console.log(config.account2.name);; w! _1 @; k: F4 @" `, D: D- z; I
console.log(account2);# @9 `0 Z; J0 \. o( O
2 Z6 b: K0 K2 m1 B' q; R
const contractAccount = fibosClient.getTableRowsSync(true, 'eosio.token', config.contractAccount.name, 'accounts');# x& l, c7 i7 t. h |
/ I' t: o! u5 g/ M
console.log(config.contractAccount.name);
$ E$ ^ W7 c' b/ \
console.log(contractAccount);/ o" r! k }5 H4 O1 [# \8 k& x
7 ]5 F+ {9 I+ ]6 Q6 w7 g
执行 fibos account.js 来查看三个账户信息。 目前我们的账户还没有 FO,所以大致情况是这样的:1 P: d( P- K* S
l# P5 G5 Z3 z
用户账户:helloworld11 金额:0.0000 FO内容提供者账户:helloworld22 金额:0.0000 FO平台合约账户:helloworld33 金额:0.0000 FO$ {8 a4 q+ w2 e. e0 H
测试网会自动给每个账户发放10 EOS 的通证用以测试使用。账户中还并没有 FO 通证。所以我们再来写一个兑换脚本,用1 EOS 换一点 FO 通证。1 L* u* E3 }" a1 T$ B
const FIBOS = require('fibos.js');) G- S1 M+ O. O. m: g# N. K
. z0 _& a( n* p# V4 A, T: d
const config = require('./config');
const fibosClient = FIBOS(config.callClient);* C w& N! a- ]9 L) ?3 p
let ctx = fibosClient.contractSync('eosio.token');+ q9 o0 m' I. u- U
const r = ctx.exchangeSync(
2 t, J, P7 t, N* L1 a6 q4 K; w# t
config.account1.name,
'1.0000 EOS@eosio',% Z) R& Z( j h3 C1 B8 |
, X! o" k* @2 j g* h; S
'0.0000 FO@eosio',8 T# D2 Y4 @# a4 @& d9 ~, h
7 ]1 c0 P/ g) M
'exchange FO to EOS',0 t2 O- P# x9 o) M1 P0 i
6 Q, h# _- q7 k- ~" l
{
authorization: config.account1.name
, F8 P- H* {3 q* O6 M8 M( s) N
}
);: _6 d; p, s* \$ F6 h
console.log(r)
# Y6 B# ~/ A- }" l* }
再次执行 fibos account.js 来查看账户信息。目前我们的账户金额大致是这样的:
用户账户:helloworld11 金额:146.4245 FO内容提供者账户:helloworld22 金额:0.0000 FO平台合约账户:helloworld33 金额:0.0000 FO5 \% J2 |, i; ?# o: y+ L" H
0 G+ X2 L! K# }" t3 t( }7 U* K# d& Q
下面写个脚本 transfer.js 来执行转帐操作。
- z0 ~- E! h9 p* n( x$ j
const FIBOS = require('fibos.js');1 D+ \0 a p( \) K
: { T! a! u$ O) l5 q2 s% Y
const config = require('./config');6 }. G% ^6 K3 ]% r9 {, S0 A+ p# ~, m
const fibosClient = FIBOS(config.callClient);
let ctx = fibosClient.contractSync('eosio.token');7 U/ _: x7 C. n w6 F, j
0 P, g% H# n4 r, S' K6 j. l
const r = ctx.extransferSync(' t' P2 a3 {! f
config.account1.name, // 用户账户9 g" y) e4 f1 P1 X5 y. Y' Z Y
f, f4 u& g% t* M- |( @2 e" b k
config.contractAccount.name, // 平台合约账户
0 ]9 w* i9 I/ a5 u3 h( o R
'10.0000 FO@eosio', // 转帐金额
( P* V; J3 K; X- F3 k
config.account2.name, // 附言填写内容提供者的账户名,平台合约会给它分账8 f: C6 I; t6 Q% g- v
{& l" @! _ @! u! L$ A
' K* R0 b% o, F7 n0 f7 n! a
authorization: config.account1.name //提供用户账户的授权
}
)
console.log(r)& `) t) {, {/ a
! K. m: L# M1 h, }0 o" k; A& l) i
我们要从用户账户 account1 给平台合约账户 account3 转帐 10 FO。memo 参数为要分成的内容提供者账户 account2。根据合约中定的2/8分成,平台合约账户 account3 将会分得2 FO,而内容提供者账户 account2 将会获得8 FO。
7 t7 V3 Z" O! W; r( m
使用命令 fibos transfer.js 执行该脚本完成转帐操作。/ r |+ F3 w! P' v
1 J# P( U0 E7 |1 w0 j- b3 Y
下面我们再来看一下目前三个账户情况。执行命令 fibos account.js。三个账户金额大致如下。3 W1 A- c. I* _7 R7 y
+ x; h1 b, g; Y
用户账户:helloworld11 金额:136.4245 FO内容提供者账户:helloworld22 金额:8.0000 FO平台合约账户:helloworld33 金额:2.0000 FO
结果显示,分账账户和平台合约账户如预期那样获得8 FO 和2 FO。2 ~ ^' H3 ~/ s: d
" C) H& v& K; g1 a! G) I; Z
5 X" |2 L- ]- {' R/ ~6 V
综上,我们成功使用了智能合约实现了自动分账。平台方还可以继续根据自己业务需要定制自己的合约。; n: P! T* w. \6 o# ]) q% a. ~
文中的代码请参考:https://github.com/fengluo/fibos-subaccount
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
成为第一个吐槽的人