├── .gitignore ├── README.md ├── _config.yml ├── babel.config.js ├── contract ├── README.md ├── config.js ├── deploy.js ├── extract.js ├── package.json └── src │ ├── abi.json │ ├── index.js │ └── otc.js ├── index.html ├── package.json ├── public ├── favicon.ico └── index.html ├── src ├── App.vue ├── assets │ ├── alipay.png │ ├── bitgxc.jpg │ ├── group.png │ ├── group2.png │ ├── guess.ico │ └── wechat.png ├── components │ └── Otc.vue ├── i18n │ ├── i18n.js │ └── langs │ │ ├── cn.js │ │ ├── en.js │ │ └── index.js ├── main.js └── router │ └── index.js ├── vue.config.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log 5 | yarn-error.log 6 | 7 | # Editor directories and files 8 | .idea 9 | *.suo 10 | *.ntvs* 11 | *.njsproj 12 | *.sln 13 | .vscode -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FO decentralized OTC 2 | 3 | > A fibos DAPP project 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | npm install 10 | 11 | # serve with hot reload at localhost:8080 12 | npm run serve 13 | 14 | # build for production with minification 15 | npm run build 16 | ``` 17 | 18 | # deotc 19 | [https://deotc.qingah.com/](https://deotc.qingah.com) -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /contract/README.md: -------------------------------------------------------------------------------- 1 | # extract Smart Contract 2 | 3 | 合约名称为: fibosuserp2p, 可以自行导出合约 4 | 5 | ## Install 6 | 7 | $`curl -s https://fibos.io/download/installer.sh | sh` 8 | 9 | $`$fibos --install` 10 | 11 | ## Extract 12 | 13 | ``` 14 | let extract = require("./extract"); 15 | 16 | extract("fibosuserp2p", { 17 | env: "mainnet", 18 | path: "./dapppath" 19 | }); 20 | ``` 21 | 22 | `fibos ...` 23 | 24 | ## Deploy 25 | 26 | testNet 27 | 28 | `fibos deploy.js test` 29 | 30 | prod 31 | 32 | `fibos deploy.js prod` -------------------------------------------------------------------------------- /contract/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | test: { 3 | chainId: "68cee14f598d88d340b50940b6ddfba28c444b46cd5f33201ace82c78896793a", 4 | httpEndpoint: "http://api.testnet.fo", 5 | contract: 'fibosuserp2p', 6 | privatekey: '', // you prvatekey 7 | publicKey: 'FO6tkap4M3oanj9HVFAvkNugcHbPeV5QRZZxKnP1y6vqiM8rniZN' 8 | }, 9 | prod: { 10 | chainId: "6aa7bd33b6b45192465afa3553dedb531acaaff8928cf64b70bd4c5e49b7ec6a", 11 | httpEndpoint: "http://to-rpc.fibos.io:8870", 12 | contract: 'fibosuserp2p', 13 | privatekey: '', // you prvatekey 14 | publicKey: 'FO6tkap4M3oanj9HVFAvkNugcHbPeV5QRZZxKnP1y6vqiM8rniZN' 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /contract/deploy.js: -------------------------------------------------------------------------------- 1 | var FIBOS = require('fibos.js'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | var config_data = require('./config.js'); 5 | 6 | var config = config_data.test 7 | if (process.argv[2]) { 8 | if (process.argv[2] === 'prod') { 9 | config = config_data.prod 10 | } else if(process.argv[2] === 'test') { 11 | config = config_data.test 12 | } 13 | } 14 | // 创建合约账户 15 | var name = config.contract; 16 | 17 | var pubkey = config.publicKey; 18 | 19 | fibos = FIBOS({ 20 | chainId: config.chainId, 21 | keyProvider: config.privatekey, 22 | httpEndpoint: config.httpEndpoint, 23 | logger: { 24 | log: null, 25 | error: null 26 | } 27 | }); 28 | 29 | // 发布一个合约 30 | 31 | var dir = 'src' 32 | var log = null 33 | // 部署abi 34 | var abi = JSON.parse(fs.readTextFile(path.join(dir, 'abi.json'))); 35 | log = fibos.setabiSync(name, abi); 36 | console.log(log); 37 | 38 | // 部署代码 39 | const js_code = fs.readTextFile(path.join(dir, 'index.js')); 40 | log = fibos.setcodeSync(name, 0, 0, fibos.compileCode(js_code)); 41 | console.log(log); 42 | 43 | // 把 合约的 权限赋予给 "eosio.code",让合约代码拥有转账的权限 44 | log = fibos.updateauthSync({ 45 | account: name, 46 | permission: "active", 47 | parent: 'owner', 48 | auth: { 49 | threshold: 1, 50 | keys: [{ 51 | key: pubkey, // 你的公钥 52 | weight: 1 53 | }], 54 | "accounts": [{ 55 | "permission": { 56 | "actor": name, 57 | "permission": "eosio.code" 58 | }, 59 | "weight": 1 60 | }] 61 | } 62 | }, { 63 | authorization: name 64 | }); 65 | console.log(log) -------------------------------------------------------------------------------- /contract/extract.js: -------------------------------------------------------------------------------- 1 | const FIBOS = require("fibos.js"); 2 | const fs = require('fs'); 3 | const zip = require('zip'); 4 | const Config = { 5 | dev: { 6 | httpEndpoint: "http://127.0.0.1:8801" 7 | }, 8 | testnet: { 9 | httpEndpoint: "http://testnet.fibos.fo:8870" 10 | }, 11 | mainnet: { 12 | httpEndpoint: "http://to-rpc.fibos.io:8870" 13 | } 14 | } 15 | 16 | module.exports = (name, options) => { 17 | 18 | options = options || {}; 19 | 20 | let env = options.env || "mainnet"; 21 | let path = options.path || "./" + name + "_" + new Date().getTime(); 22 | 23 | let config = Config[env]; 24 | 25 | let fibos = FIBOS({ 26 | httpEndpoint: config.httpEndpoint, 27 | logger: { 28 | log: null, 29 | error: null 30 | } 31 | }); 32 | 33 | fs.mkdir(path); 34 | 35 | let abi = fibos.getAbiSync(name); 36 | fs.writeFile(path + "/abi.json", JSON.stringify(abi)); 37 | 38 | let code = fibos.getRawCodeAndAbiSync(name); 39 | let zipfile = zip.open(Buffer.from(code.wasm, 'base64')); 40 | zipfile.extractAll(path) 41 | } -------------------------------------------------------------------------------- /contract/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "deotc", 3 | "version": "1.0.0", 4 | "main": "./extract.js", 5 | "description": "", 6 | "repository": "", 7 | "keywords": "", 8 | "author": "", 9 | "license": "ISC", 10 | "dependencies": { 11 | "fibos.js": "^0.3.1" 12 | } 13 | } -------------------------------------------------------------------------------- /contract/src/abi.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "eosio::abi/1.0", 3 | "structs": [ 4 | { 5 | "name": "config", 6 | "base": "", 7 | "fields": [ 8 | { 9 | "name": "id", 10 | "type": "int64" 11 | }, 12 | { 13 | "name": "value", 14 | "type": "int64" 15 | } 16 | ] 17 | }, 18 | { 19 | "name": "result", 20 | "base": "", 21 | "fields": [ 22 | { 23 | "name": "id", 24 | "type": "int64" 25 | } 26 | ] 27 | }, 28 | { 29 | "name": "arbitrate", 30 | "base": "", 31 | "fields": [ 32 | { 33 | "name": "id", 34 | "type": "int64" 35 | }, 36 | { 37 | "name": "win", 38 | "type": "uint8" 39 | }, 40 | { 41 | "name": "memo", 42 | "type": "string" 43 | } 44 | ] 45 | }, 46 | { 47 | "name": "setinfo", 48 | "base": "", 49 | "fields": [ 50 | { 51 | "name": "id", 52 | "type": "int64" 53 | }, 54 | { 55 | "name": "acc", 56 | "type": "string" 57 | }, 58 | { 59 | "name": "ref", 60 | "type": "name" 61 | } 62 | ] 63 | }, 64 | { 65 | "name": "setgroup", 66 | "base": "", 67 | "fields": [ 68 | { 69 | "name": "acc", 70 | "type": "string" 71 | } 72 | ] 73 | }, 74 | { 75 | "name": "buy", 76 | "base": "", 77 | "fields": [ 78 | { 79 | "name": "id", 80 | "type": "int64" 81 | }, 82 | { 83 | "name": "pay", 84 | "type": "asset" 85 | } 86 | ] 87 | }, 88 | { 89 | "name": "applyarb", 90 | "base": "", 91 | "fields": [ 92 | { 93 | "name": "id", 94 | "type": "int64" 95 | } 96 | ] 97 | }, 98 | { 99 | "name": "appeal", 100 | "base": "", 101 | "fields": [ 102 | { 103 | "name": "id", 104 | "type": "int64" 105 | } 106 | ] 107 | }, 108 | { 109 | "name": "withdraw", 110 | "base": "", 111 | "fields": [ 112 | { 113 | "name": "id", 114 | "type": "int64" 115 | } 116 | ] 117 | }, 118 | { 119 | "name": "deltable", 120 | "base": "", 121 | "fields": [ 122 | { 123 | "name": "table", 124 | "type": "string" 125 | }, 126 | { 127 | "name": "scope", 128 | "type": "name" 129 | }, 130 | { 131 | "name": "id", 132 | "type": "int64" 133 | } 134 | ] 135 | }, 136 | { 137 | "name": "updateorder", 138 | "base": "", 139 | "fields": [ 140 | { 141 | "name": "id", 142 | "type": "int64" 143 | }, 144 | { 145 | "name": "price", 146 | "type": "uint32" 147 | } 148 | ] 149 | }, 150 | { 151 | "name": "cancelorder", 152 | "base": "", 153 | "fields": [ 154 | { 155 | "name": "id", 156 | "type": "int64" 157 | } 158 | ] 159 | }, 160 | { 161 | "name": "black_account", 162 | "base": "", 163 | "fields": [ 164 | { 165 | "name": "id", 166 | "type": "name" 167 | }, 168 | { 169 | "name": "account", 170 | "type": "name" 171 | } 172 | ] 173 | }, 174 | { 175 | "name": "player", 176 | "base": "", 177 | "fields": [ 178 | { 179 | "name": "id", 180 | "type": "name" 181 | }, 182 | { 183 | "name": "record_id", 184 | "type": "int64" 185 | }, 186 | { 187 | "name": "success_count", 188 | "type": "int32" 189 | }, 190 | { 191 | "name": "arbitrate_count", 192 | "type": "int32" 193 | }, 194 | { 195 | "name": "cur_count", 196 | "type": "int32" 197 | }, 198 | { 199 | "name": "ref", 200 | "type": "name" 201 | } 202 | ] 203 | }, 204 | { 205 | "name": "account", 206 | "base": "", 207 | "fields": [ 208 | { 209 | "name": "primary", 210 | "type": "uint64" 211 | }, 212 | { 213 | "name": "balance", 214 | "type": "extended_asset" 215 | } 216 | ] 217 | }, 218 | { 219 | "name": "global_state", 220 | "base": "", 221 | "fields": [ 222 | { 223 | "name": "id", 224 | "type": "int64" 225 | }, 226 | { 227 | "name": "value", 228 | "type": "int64" 229 | } 230 | ] 231 | }, 232 | { 233 | "name": "arbitrate_state", 234 | "base": "", 235 | "fields": [ 236 | { 237 | "name": "id", 238 | "type": "int64" 239 | }, 240 | { 241 | "name": "record_id", 242 | "type": "int64" 243 | }, 244 | { 245 | "name": "account", 246 | "type": "name" 247 | }, 248 | { 249 | "name": "dateline", 250 | "type": "uint64" 251 | }, 252 | { 253 | "name": "win", 254 | "type": "uint8" 255 | }, 256 | { 257 | "name": "memo", 258 | "type": "string" 259 | } 260 | ] 261 | }, 262 | { 263 | "name": "userinfo", 264 | "base": "", 265 | "fields": [ 266 | { 267 | "name": "id", 268 | "type": "int64" 269 | }, 270 | { 271 | "name": "acct", 272 | "type": "string" 273 | } 274 | ] 275 | }, 276 | { 277 | "name": "arbgroup", 278 | "base": "", 279 | "fields": [ 280 | { 281 | "name": "account", 282 | "type": "name" 283 | }, 284 | { 285 | "name": "acct", 286 | "type": "string" 287 | } 288 | ] 289 | }, 290 | { 291 | "name": "record", 292 | "base": "", 293 | "fields": [ 294 | { 295 | "name": "id", 296 | "type": "int64" 297 | }, 298 | { 299 | "name": "seller", 300 | "type": "name" 301 | }, 302 | { 303 | "name": "buyer", 304 | "type": "name" 305 | }, 306 | { 307 | "name": "dateline", 308 | "type": "uint64" 309 | }, 310 | { 311 | "name": "pay", 312 | "type": "asset" 313 | }, 314 | { 315 | "name": "buy_pay", 316 | "type": "asset" 317 | }, 318 | { 319 | "name": "status", 320 | "type": "uint8" 321 | }, 322 | { 323 | "name": "price", 324 | "type": "uint32" 325 | }, 326 | { 327 | "name": "type", 328 | "type": "uint8" 329 | }, 330 | { 331 | "name": "pay_type", 332 | "type": "uint8" 333 | } 334 | ] 335 | }, 336 | { 337 | "name": "tmp_info", 338 | "base": "", 339 | "fields": [ 340 | { 341 | "name": "id", 342 | "type": "int64" 343 | }, 344 | { 345 | "name": "json", 346 | "type": "string" 347 | } 348 | ] 349 | }, 350 | { 351 | "name": "producer_info", 352 | "base": "", 353 | "fields": [ 354 | {"name":"owner", "type":"name"}, 355 | {"name":"total_votes", "type":"float64"}, 356 | {"name":"producer_key", "type":"public_key"}, 357 | {"name":"is_active", "type":"bool"}, 358 | {"name":"url", "type":"string"}, 359 | {"name":"unpaid_blocks", "type":"uint32"}, 360 | {"name":"last_claim_time", "type":"uint64"}, 361 | {"name":"location", "type":"uint16" 362 | }] 363 | } 364 | ], 365 | "actions": [ 366 | { 367 | "name": "config", 368 | "type": "config", 369 | "ricardian_contract": "" 370 | }, 371 | { 372 | "name": "setinfo", 373 | "type": "setinfo", 374 | "ricardian_contract": "" 375 | }, 376 | { 377 | "name": "result", 378 | "type": "result", 379 | "ricardian_contract": "" 380 | }, 381 | { 382 | "name": "buy", 383 | "type": "buy", 384 | "ricardian_contract": "" 385 | }, 386 | { 387 | "name": "applyarb", 388 | "type": "applyarb", 389 | "ricardian_contract": "" 390 | }, 391 | { 392 | "name": "appeal", 393 | "type": "appeal", 394 | "ricardian_contract": "" 395 | }, 396 | { 397 | "name": "withdraw", 398 | "type": "withdraw", 399 | "ricardian_contract": "" 400 | }, 401 | { 402 | "name": "deltable", 403 | "type": "deltable", 404 | "ricardian_contract": "" 405 | }, 406 | { 407 | "name": "updateorder", 408 | "type": "updateorder", 409 | "ricardian_contract": "" 410 | }, 411 | { 412 | "name": "cancelorder", 413 | "type": "cancelorder", 414 | "ricardian_contract": "" 415 | }, 416 | { 417 | "name": "arbitrate", 418 | "type": "arbitrate", 419 | "ricardian_contract": "" 420 | }, 421 | { 422 | "name": "setgroup", 423 | "type": "setgroup", 424 | "ricardian_contract": "" 425 | } 426 | ], 427 | "tables": [ 428 | { 429 | "name": "global", 430 | "type": "global_state", 431 | "index_type": "i64", 432 | "key_names": [ 433 | "id" 434 | ], 435 | "key_types": [ 436 | "int64" 437 | ] 438 | }, 439 | { 440 | "name": "players", 441 | "type": "player", 442 | "index_type": "i64", 443 | "key_names": [ 444 | "id" 445 | ], 446 | "key_types": [ 447 | "name" 448 | ] 449 | }, 450 | { 451 | "name": "blacklist", 452 | "type": "black_account", 453 | "index_type": "i64", 454 | "key_names": [ 455 | "id" 456 | ], 457 | "key_types": [ 458 | "name" 459 | ] 460 | }, 461 | { 462 | "name": "accounts", 463 | "type": "account", 464 | "index_type": "i64", 465 | "key_names": [ 466 | "primary" 467 | ], 468 | "key_types": [ 469 | "uint64" 470 | ] 471 | }, 472 | { 473 | "name": "records", 474 | "type": "record", 475 | "index_type": "i64", 476 | "key_names": [ 477 | "id" 478 | ], 479 | "key_types": [ 480 | "int64" 481 | ] 482 | }, 483 | { 484 | "name": "arbitration", 485 | "type": "arbitrate_state", 486 | "index_type": "i64", 487 | "key_names": [ 488 | "id" 489 | ], 490 | "key_types": [ 491 | "int64" 492 | ] 493 | }, 494 | { 495 | "name": "userinfos", 496 | "type": "userinfo", 497 | "index_type": "i64", 498 | "key_names": [ 499 | "id" 500 | ], 501 | "key_types": [ 502 | "int64" 503 | ] 504 | }, 505 | { 506 | "name": "arbgroups", 507 | "type": "arbgroup", 508 | "index_type": "i64", 509 | "key_names": [ 510 | "account" 511 | ], 512 | "key_types": [ 513 | "name" 514 | ] 515 | }, 516 | { 517 | "name": "tmps", 518 | "type": "tmp_info", 519 | "index_type": "i64", 520 | "key_names" : ["owner"], 521 | "key_types" : ["name"] 522 | }, 523 | { 524 | "name": "producers", 525 | "type": "producer_info", 526 | "index_type": "i64", 527 | "key_names" : ["owner"], 528 | "key_types" : ["name"] 529 | } 530 | ] 531 | } -------------------------------------------------------------------------------- /contract/src/index.js: -------------------------------------------------------------------------------- 1 | const CONTRACT_NAME="fibosuserp2p",error_msg=["no this id record","USER NO TOKEN","not your seller","status error","NEED FO","arbitrate NEED MORE 1000000","fuck you, blacklist","sell number > buy number","sell token = buy token","rich NEED MORE 200000"];function sendToken(t,e,a,r){trans.send_inline("eosio.token","transfer",{from:t,to:e,quantity:a,memo:r},[{actor:t,permission:"active"}])}function getBalance(t,e="FO",a="eosio.token"){let r,s=db.accounts(a,t).begin();for(;s.data&&((r=s.data.balance.quantity.split(" "))[0]=Number(r[0]),r[1]!==e);)s=s.next();return assert(s.data,error_msg[1]),r}function getPlayer(t){const e=db.players(CONTRACT_NAME,CONTRACT_NAME);return{players:e,player:e.find(t)}}const record_indexes={seller:[64,t=>[t.seller]],buyer:[64,t=>[t.buyer]]};function getRecord(t){const e=db.records(CONTRACT_NAME,CONTRACT_NAME,record_indexes);return{record:e.find(t),records:e}}function getAccount(){let t=action.authorization[0].actor;return action.require_auth(t),t}function assert_arbitrate(t){const e=db.producers("eosio","eosio").find(t),a=getBalance(t);assert(Number(a[0])>getConfig(10)||e.data&&Number(e.data.total_votes)>getConfig(1),"need bp or richer")}function assert_userinfo(t){let e=db.userinfos(CONTRACT_NAME,t).begin();assert(e.data,"需要先设置支付信息")}function assert_sell_buy(t,e){const a=t.split(" "),r=e.split(" ");assert(Number(a[0])>=Number(r[0]),error_msg[7]),assert(a[1]===r[1],error_msg[8])}function assert_maintenance(){assert(0==getConfig(6),"系统维护中")}function assert_player_count(t){const{player:e}=getPlayer(t);assert(e.data&&e.data.cur_count-e.data.success_count{action.require_auth(CONTRACT_NAME);const a=db.global(CONTRACT_NAME,CONTRACT_NAME).find(t);a.data?(a.data.value=e,a.update(CONTRACT_NAME)):a.emplace(CONTRACT_NAME,{id:t,value:e})}),exports.deltable=((t,e,a)=>{action.require_auth(CONTRACT_NAME),assert(db[t],"no this table");const r=db[t](CONTRACT_NAME,e);if(a){const t=r.find(a);assert(record.data,"need have data"),t.remove()}else{let t,e=r.begin(),a=0;do{t=e.next(),e.remove(),e=t,a++}while(a<30&&e.data)}});const arbitrate_indexes={record:[64,t=>[t.record_id]],account:[64,t=>[t.account]]};function delarbs(t){let e,a=db.arbitration(CONTRACT_NAME,CONTRACT_NAME,arbitrate_indexes).indexes.record.find({record_id:t});for(;a.data;)e=a.next(),a.remove(),a=e}function _after_cur_count(t){const{player:e}=getPlayer(t);e.data&&(e.data.cur_count--,e.update(CONTRACT_NAME))}exports.on_transfer=((t,e,a,r)=>{if(t===CONTRACT_NAME||e!==CONTRACT_NAME)return;const s=a.split(" "),d=r.split(","),o=d[0],n=action.publication_time;if(assert_userinfo(t),assert_player_count(t),"1"===o){const e=Number(d[1]),r=Number(d[2]),s=db.records(CONTRACT_NAME,CONTRACT_NAME,record_indexes),o=s.get_primary_key(),c={id:o,seller:t,buyer:"",dateline:n,pay:a,buy_pay:a,status:1,price:e,type:1,pay_type:r};s.emplace(CONTRACT_NAME,c),update_record_id(t,o)}else if("2"===o){const e=Number(d[1]),r=d[2],o=Number(d[3]);assert(Number(s[0])==getConfig(7)/1e4,"1.0000 FO 服务费");const c=db.records(CONTRACT_NAME,CONTRACT_NAME,record_indexes),u=c.get_primary_key(),i={id:u,seller:CONTRACT_NAME,buyer:t,dateline:n,pay:a,buy_pay:r,status:1,price:e,type:2,pay_type:o};c.emplace(CONTRACT_NAME,i),update_record_id(t,u)}else if("3"===o){const e=Number(d[1]),r=Number(d[2]),o=getBalance(t);assert(o,error_msg[4]),assert(o[0]>=getConfig(2),error_msg[5]),assert("FO"===o[1],"进场单只接受FO"),assert(Number(s[0])==getConfig(8)/1e4,"1000.0000 FO 抵押费用");const c=db.records(CONTRACT_NAME,CONTRACT_NAME,record_indexes),u=c.get_primary_key(),i={id:u,seller:t,buyer:CONTRACT_NAME,dateline:n,pay:a,buy_pay:a,status:1,price:e,type:3,pay_type:r};c.emplace(CONTRACT_NAME,i),update_record_id(t,u)}else if("4"===o){const e=Number(d[1]),r=Number(d[2]),s=db.records(CONTRACT_NAME,CONTRACT_NAME,record_indexes).find(e);assert(s.data,error_msg[0]),assert(2==s.data.type,"需为用户买单"),assert(1==s.data.status,"未锁定订单"),assert_sell_buy(a,s.data.buy_pay),s.data.status=2,s.data.seller=t,s.data.pay=a,s.data.dateline=n,r>-1&&(s.data.pay_type=r),s.update(CONTRACT_NAME),update_record_id(t,e)}}),exports.setinfo=((t,e,a)=>{const r=getAccount(),s=db.userinfos(CONTRACT_NAME,r),d=s.find(t);d.data?(d.data.acct=e,d.update(r)):s.emplace(r,{id:t,acct:e});const{player:o,players:n}=getPlayer(r);if(!o.data){a&&action.is_account(a);const t={id:r,record_id:-1,success_count:0,arbitrate_count:0,cur_count:0,ref:a};n.emplace(CONTRACT_NAME,t)}}),exports.buy=((t,e)=>{const a=getAccount(),{record:r}=getRecord(t);assert(r.data,error_msg[0]),assert(1==r.data.type,"常规订单,用户才能链上购买"),assert(1==r.data.status,error_msg[1]),assert_sell_buy(r.data.pay,e);const{player:s,players:d}=getPlayer(a);if(s.data)s.data.cur_count++,s.update(CONTRACT_NAME);else{const e={id:a,record_id:t,success_count:0,arbitrate_count:0,cur_count:1,ref:""};d.emplace(CONTRACT_NAME,e)}assert_player_count(a);const o=getBalance(a);assert("FO"===o[1],"只接受FO"),assert(o[0]>100,"请先进场,账号需要大于100FO"),r.data.status=2,r.data.buy_pay=e,r.data.buyer=a,r.update(CONTRACT_NAME)}),exports.updateorder=((t,e)=>{const a=getAccount(),{record:r}=getRecord(t);assert(r.data,error_msg[0]),assert(1==r.data.status,error_msg[1]),1==r.data.type||3==r.data.type?assert(r.data.seller===a,"本人订单"):2==r.data.type?assert(r.data.buyer===a,"本人订单"):assert(!1,"订单错误"),r.data.price=e,r.update(CONTRACT_NAME)}),exports.cancelorder=(t=>{const e=getAccount(),{record:a}=getRecord(t);if(assert(a.data,error_msg[0]),a.data.seller===e){assert(1==a.data.status,error_msg[1]);let t=a.data.pay;if(3==a.data.type){const e=a.data.pay.split(" "),r=e[0].split(".")[1].length;t=(Number(e[0])-getConfig(6)/1e4).toFixed(r)+" "+e[1]}_after_cur_count(e),sendToken(CONTRACT_NAME,e,t,"退回订单pay"),a.remove()}else a.data.buyer===e?1==a.data.type?(assert(a.data.status>=2,error_msg[1]),_after_cur_count(e),a.data.status=1,a.data.buyer="",a.update(CONTRACT_NAME)):2==a.data.type?(assert(1==a.data.status,error_msg[1]),_after_cur_count(e),a.remove()):assert(!1,"其它订单不能取消"):assert(!1,"非卖家/买家不能取消订单");delarbs(t)}),exports.result=(t=>{const e=getAccount(),{record:a}=getRecord(t);assert(a.data,error_msg[0]),assert(a.data.seller===e,error_msg[2]),assert(a.data.status>=2,error_msg[3]);const r=a.data.buy_pay,s=a.data.pay.split(" "),d=s[0].split(".")[1].length,o=a.data.buy_pay.split(" ");assert(s[1]===o[1],"buy === sell asset!");const n=Number(s[0])-Number(o[0]),c=db.players(CONTRACT_NAME,CONTRACT_NAME),u=c.find(a.data.buyer);u.data&&(u.data.cur_count--,u.update(CONTRACT_NAME)),sendToken(CONTRACT_NAME,a.data.buyer,r,"pay success"),n>0?(a.data.pay=n.toFixed(d)+" "+s[1],a.data.buyer="",a.data.status=1,a.update(CONTRACT_NAME)):a.remove();const i=c.find(e);i.data&&(i.data.success_count++,i.update(CONTRACT_NAME)),delarbs(t)}),exports.applyarb=(t=>{const e=getAccount(),{record:a}=getRecord(t);assert(a.data,error_msg[0]),assert(e===a.data.seller||e===a.data.buyer,"需要买家或卖家"),assert(parseInt(a.data.dateline)+parseInt(getConfig(3)){const r=getAccount(),s=db.blacklist(CONTRACT_NAME,CONTRACT_NAME).find(r);assert(!s.data,error_msg[6]),assert(1==e||2==e,error_msg[5]);const{record:d}=getRecord(t);assert(d.data,error_msg[0]),assert_arbitrate(r);const o=db.arbitration(CONTRACT_NAME,CONTRACT_NAME,arbitrate_indexes),n=action.publication_time;let c=o.indexes.record.find({record_id:t}),u=0;for(;c.data;)assert(c.data.account!==r,"该账号,已仲裁"),u++,c=c.next();u>0&&assert(5==d.data.status,"当一个仲裁人裁决后, 如上诉,在有仲裁人加入时,订单状态必须为上诉仲裁中"),3==d.data.status&&(d.data.status=4),d.update(CONTRACT_NAME),o.emplace(CONTRACT_NAME,{id:o.get_primary_key(),record_id:t,account:r,dateline:n,win:e,memo:a})}),exports.appeal=(t=>{const e=getAccount(),{record:a}=getRecord(t);assert(a.data,error_msg[0]),assert(e===a.data.seller||e===a.data.buyer,"需要买家或卖家"),assert(4==a.data.status,error_msg[3]),a.data.status=5,a.data.dateline=action.publication_time,a.update(CONTRACT_NAME)}),exports.withdraw=(t=>{const e=getAccount(),{record:a}=getRecord(t);assert(a.data,error_msg[0]),assert(e===a.data.seller||e===a.data.buyer,"需要买家或卖家");let r=db.arbitration(CONTRACT_NAME,CONTRACT_NAME,arbitrate_indexes).indexes.record.find({record_id:t});const s=action.publication_time;if(4==a.data.status){if(assert(parseInt(a.data.dateline)+parseInt(getConfig(4))o&&d>=c){const t=db.players(CONTRACT_NAME,CONTRACT_NAME),e=t.find(a.data.buyer);e.data&&(e.data.cur_count--,e.data.arbitrate_count++,e.update(CONTRACT_NAME)),a.data.status=1,a.data.buyer="",a.update(CONTRACT_NAME);const r=t.find(a.data.seller);r.data&&(r.data.arbitrate_count++,r.update(CONTRACT_NAME))}else if(o>d&&o>=c){const t=db.players(CONTRACT_NAME,CONTRACT_NAME),e=t.find(a.data.buyer);e.data&&(e.data.success_count++,e.data.arbitrate_count++,e.update(CONTRACT_NAME));const r=t.find(a.data.seller);r.data&&(r.data.cur_count--,r.data.arbitrate_count++,r.update(CONTRACT_NAME)),sendToken(CONTRACT_NAME,a.data.buyer,a.data.buy_pay,"appeal success"),a.remove()}else assert(!1,"还不能提现");for(;r.data;)n=r.next(),r.remove(),r=n}),exports.setgroup=(t=>{const e=getAccount();assert_arbitrate(e);const a=db.arbgroups(CONTRACT_NAME,CONTRACT_NAME),r=a.find(e);r.data?(r.data.acct=t,r.update(e)):a.emplace(CONTRACT_NAME,{account:e,acct:t})}); -------------------------------------------------------------------------------- /contract/src/otc.js: -------------------------------------------------------------------------------- 1 | const CONTRACT_NAME = 'fibosuserp2p'; 2 | 3 | const error_msg = [ 4 | 'no this id record', 5 | 'USER NO TOKEN', 6 | 'not your seller', 7 | 'status error', 8 | 'NEED FO', 9 | 'arbitrate NEED MORE 1000000', 10 | 'fuck you, blacklist', 11 | 'sell number > buy number', 12 | 'sell token = buy token', 13 | 'rich NEED MORE 200000', 14 | ]; 15 | 16 | // 17 | function sendToken (from, to, quantity, memo) { 18 | trans.send_inline("eosio.token", "transfer", { 19 | from: from, 20 | to: to, 21 | quantity: quantity, 22 | memo: memo 23 | }, [{ 24 | "actor": from, 25 | "permission": "active" 26 | }]) 27 | } 28 | 29 | // 获取用户TOKEN 30 | function getBalance (account, asset = 'FO', contract = 'eosio.token') { 31 | const accounts = db.accounts(contract, account); 32 | let itr = accounts.begin(); 33 | let bAsset; 34 | while (itr.data) { 35 | bAsset = itr.data.balance.quantity.split(' '); 36 | bAsset[0] = Number(bAsset[0]) 37 | if (bAsset[1] === asset) { 38 | break; 39 | } 40 | itr = itr.next(); 41 | } 42 | assert(itr.data, error_msg[1]); 43 | return bAsset; 44 | } 45 | 46 | function getPlayer (account) { 47 | const players = db.players(CONTRACT_NAME, CONTRACT_NAME); 48 | const player = players.find(account); 49 | return { 50 | players, 51 | player 52 | } 53 | } 54 | 55 | const record_indexes = { 56 | seller: [64, o => [o.seller]], 57 | buyer: [64, o => [o.buyer]], 58 | }; 59 | 60 | function getRecord(id) { 61 | const records = db.records(CONTRACT_NAME, CONTRACT_NAME, record_indexes); 62 | const record = records.find(id); 63 | return { 64 | record, 65 | records, 66 | } 67 | } 68 | 69 | function getAccount() { 70 | let account = action.authorization[0].actor; 71 | action.require_auth(account); 72 | return account; 73 | } 74 | 75 | // 判断账号是否允许仲裁 76 | function assert_arbitrate (account) { 77 | const prods = db.producers('eosio', 'eosio'); 78 | const producer = prods.find(account); 79 | const balance = getBalance(account); 80 | assert(Number(balance[0]) > getConfig(10) || (producer.data && Number(producer.data.total_votes) > getConfig(1)), 'need bp or richer'); 81 | } 82 | 83 | function assert_userinfo (account) { 84 | const userinfos = db.userinfos(CONTRACT_NAME, account); 85 | let userinfo = userinfos.begin(); 86 | assert(userinfo.data, '需要先设置支付信息'); 87 | } 88 | 89 | function assert_sell_buy (sell, buy) { 90 | const sell_asset = sell.split(' '); 91 | const pay_asset = buy.split(' '); 92 | 93 | assert(Number(sell_asset[0]) >= Number(pay_asset[0]), error_msg[7]); 94 | assert(sell_asset[1] === pay_asset[1], error_msg[8]); 95 | } 96 | 97 | function assert_maintenance () { 98 | assert(getConfig(6) == 0, '系统维护中'); 99 | } 100 | 101 | function assert_player_count (account) { 102 | const { player } = getPlayer(account); 103 | assert(player.data && player.data.cur_count - player.data.success_count < getConfig(9), '用户不能部署多个订单'); 104 | } 105 | 106 | 107 | const global = db.global(CONTRACT_NAME, CONTRACT_NAME); 108 | function getConfig(id) { 109 | const state = global.find(id); 110 | if (state.data) { 111 | return state.data.value 112 | } 113 | const default_config = [ 114 | // 仲裁超过3个bp认准,最终胜诉 115 | arbitrater_count = 3, 116 | // 仲裁bp需要投票数 117 | bp_total_votes = 300000000000000000, 118 | // 大户的FO数量定义 119 | rich_limit = 20000, 120 | // 仲裁时间 121 | arbitrate_timeout = 3600 * 12 * 1000 * 1000, 122 | // 上诉时间 123 | appeal_timeout = 3600 * 12 * 1000 * 1000, 124 | // 每单限制,以毫钱为单位 125 | pay_limit = 20000000, 126 | // 系统维护中 127 | is_maintenance = 0, 128 | // 服务费用 129 | service_pay = 10000, 130 | // 抵押费用 131 | staking_pay = 10000000, 132 | // 限制每个用户布单数 133 | user_record_limit = 2, 134 | // 仲裁大户数量 135 | arb_rich_limit = 500000, 136 | ] 137 | return default_config[id]; 138 | } 139 | 140 | /** 141 | * @param id 142 | * @returns 143 | */ 144 | exports.config = (id, value) => { 145 | action.require_auth(CONTRACT_NAME); 146 | const global = db.global(CONTRACT_NAME, CONTRACT_NAME); 147 | const global_state = global.find(id); 148 | if (global_state.data) { 149 | global_state.data.value = value; 150 | global_state.update(CONTRACT_NAME); 151 | } else { 152 | global_state.emplace(CONTRACT_NAME, { 153 | "id": id, 154 | "value": value 155 | }); 156 | } 157 | } 158 | 159 | /** 160 | * @param table 161 | * @param id 162 | * @returns 163 | */ 164 | exports.deltable = (table, scope, id) => { 165 | action.require_auth(CONTRACT_NAME); 166 | assert(db[table], 'no this table'); 167 | const tables = db[table](CONTRACT_NAME, scope); 168 | if (id) { 169 | const red = tables.find(id); 170 | assert(record.data, 'need have data'); 171 | red.remove(); 172 | } else { 173 | let itr = tables.begin(); 174 | let i = 0, itr1; 175 | do { 176 | itr1 = itr.next(); 177 | itr.remove(); 178 | itr = itr1; 179 | i ++; 180 | } while (i < 30 && itr.data); 181 | } 182 | } 183 | 184 | function update_record_id (account, id) { 185 | const { player } = getPlayer(account); 186 | if (player.data) { 187 | player.data.cur_count++; 188 | player.data.record_id = id; 189 | player.update(CONTRACT_NAME); 190 | } 191 | } 192 | 193 | const arbitrate_indexes = { 194 | record: [64, o => [o.record_id]], 195 | account: [64, o => [o.account]], 196 | }; 197 | 198 | function delarbs (id) { 199 | const arbitration = db.arbitration(CONTRACT_NAME, CONTRACT_NAME, arbitrate_indexes); 200 | let upper_itr = arbitration.indexes.record.find({record_id:id}); 201 | let next_itr; 202 | while (upper_itr.data) { 203 | next_itr = upper_itr.next(); 204 | upper_itr.remove(); 205 | upper_itr = next_itr; 206 | } 207 | } 208 | 209 | /** 210 | * @param account 211 | * @param user 212 | * @param amount 213 | * @param memo 214 | * @returns 215 | */ 216 | exports.on_transfer = (account, user, amount, memo) => { 217 | if (account === CONTRACT_NAME || user !== CONTRACT_NAME) { 218 | return; 219 | } 220 | 221 | const amount_asset = amount.split(' '); 222 | const params = memo.split(','); 223 | const act = params[0]; 224 | const cur_time = action.publication_time; 225 | 226 | assert_userinfo(account); 227 | assert_player_count(account); 228 | // status 1:挂卖单,2:锁定订单,3:申请仲裁,4:仲裁中,5:上诉仲裁中 229 | // act 1:卖家增加卖单 2:买家增加买单 3:进场单 4; 用户卖币锁定到指定id买单 230 | if (act === '1') { 231 | // price:单价, pay_type:收款设置 232 | const price = Number(params[1]), pay_type = Number(params[2]); 233 | const records = db.records(CONTRACT_NAME, CONTRACT_NAME, record_indexes); 234 | 235 | // 不限制了 236 | // assert(Number(amount_asset[0]) * price <= getConfig(5), '每单超过限额'); 237 | 238 | const id = records.get_primary_key(); 239 | // 用户增加卖单 240 | const record = { 241 | id: id, 242 | seller: account, 243 | buyer: '', 244 | dateline: cur_time, 245 | pay: amount, 246 | buy_pay: amount, 247 | status: 1, 248 | price: price, 249 | type: 1, 250 | pay_type 251 | }; 252 | records.emplace(CONTRACT_NAME, record); 253 | update_record_id(account, id); 254 | } else if(act === '2') { 255 | // 用户下买单 256 | // price:单价, ref:推荐人 257 | const price = Number(params[1]), buy_amount = params[2], pay_type = Number(params[3]); 258 | assert(Number(amount_asset[0]) == getConfig(7) / 10000, '1.0000 FO 服务费'); 259 | const records = db.records(CONTRACT_NAME, CONTRACT_NAME, record_indexes); 260 | const id = records.get_primary_key(); 261 | // 用户增加卖单,amount仅为下买单服务费 262 | const record = { 263 | id: id, 264 | seller: CONTRACT_NAME, 265 | buyer: account, 266 | dateline: cur_time, 267 | pay: amount, 268 | buy_pay: buy_amount, 269 | status: 1, 270 | price: price, 271 | type: 2, 272 | pay_type: pay_type,// 暂未设置 273 | } 274 | records.emplace(CONTRACT_NAME, record); 275 | update_record_id(account, id); 276 | } else if (act === '3') { 277 | // price:单价, pay_type 278 | const price = Number(params[1]), pay_type = Number(params[2]); 279 | const balance = getBalance(account); 280 | assert(balance, error_msg[4]); 281 | // 大户大于一定FO数量才能做小额无仲裁商家,无仲裁商家需要 282 | assert(balance[0] >= getConfig(2), error_msg[5]); 283 | assert(balance[1] === 'FO', '进场单只接受FO'); 284 | 285 | assert(Number(amount_asset[0]) == getConfig(8) / 10000, '1000.0000 FO 抵押费用'); 286 | const records = db.records(CONTRACT_NAME, CONTRACT_NAME, record_indexes); 287 | const id = records.get_primary_key(); 288 | // 用户增加卖单 289 | const record = { 290 | id: id, 291 | seller: account, 292 | buyer: CONTRACT_NAME, 293 | dateline: cur_time, 294 | pay: amount, 295 | buy_pay: amount, 296 | status: 1, 297 | price: price, 298 | type: 3, 299 | pay_type 300 | }; 301 | 302 | records.emplace(CONTRACT_NAME, record); 303 | // 更新用户信息 304 | update_record_id(account, id); 305 | } else if (act === '4') { 306 | // 卖方用户卖TOKEN到指定id买单 307 | // id:用户订单,pay_type: 联系支付设置 308 | const id = Number(params[1]), pay_type = Number(params[2]); 309 | 310 | // assert(Number(amount_asset[0]) == getConfig(7) / 10000, '1.0000 FO 服务费'); 311 | const records = db.records(CONTRACT_NAME, CONTRACT_NAME, record_indexes); 312 | const record = records.find(id); 313 | assert(record.data, error_msg[0]); 314 | assert(record.data.type == 2, '需为用户买单'); 315 | assert(record.data.status == 1, '未锁定订单'); 316 | 317 | assert_sell_buy(amount, record.data.buy_pay); 318 | 319 | // 锁定订单 320 | record.data.status = 2; 321 | record.data.seller = account; 322 | record.data.pay = amount; 323 | record.data.dateline = cur_time; 324 | if (pay_type>-1) { 325 | record.data.pay_type = pay_type; 326 | } 327 | 328 | record.update(CONTRACT_NAME); 329 | // 更新用户信息 330 | update_record_id(account, id); 331 | } 332 | } 333 | 334 | 335 | /** 336 | * @param id 信息类型... 337 | * @param acct 账号 338 | * @param ref 推荐人 339 | * 用户增加信息 340 | */ 341 | exports.setinfo = (id, acct, ref) => { 342 | const account = getAccount(); 343 | const userinfos = db.userinfos(CONTRACT_NAME, account); 344 | const upper_itr = userinfos.find(id); 345 | 346 | if (upper_itr.data) { 347 | upper_itr.data.acct = acct; 348 | upper_itr.update(account); 349 | } else { 350 | userinfos.emplace(account, { 351 | id: id, 352 | acct: acct 353 | }); 354 | } 355 | const { player, players } = getPlayer(account); 356 | if (!player.data) { 357 | if (ref) { 358 | action.is_account(ref); 359 | } 360 | const data = { 361 | "id": account, 362 | "record_id": -1, 363 | "success_count": 0, 364 | "arbitrate_count": 0, 365 | "cur_count": 0, 366 | "ref": ref 367 | }; 368 | players.emplace(CONTRACT_NAME, data); 369 | } 370 | } 371 | 372 | /** 373 | * @param id 订单id 374 | * @param pay 买多少 375 | * 用户买,锁定订单 376 | */ 377 | exports.buy = (id, pay) => { 378 | const account = getAccount(); 379 | const { record } = getRecord(id); 380 | assert(record.data, error_msg[0]); 381 | assert(record.data.type == 1, '常规订单,用户才能链上购买'); 382 | assert(record.data.status == 1, error_msg[1]); 383 | 384 | assert_sell_buy(record.data.pay, pay); 385 | 386 | // 更新用户信息 387 | const { player, players } = getPlayer(account); 388 | if (player.data) { 389 | player.data.cur_count++; 390 | player.update(CONTRACT_NAME); 391 | } else { 392 | const data = { 393 | "id": account, 394 | "record_id": id, 395 | "success_count": 0, 396 | "arbitrate_count": 0, 397 | "cur_count": 1, 398 | "ref": "" 399 | }; 400 | players.emplace(CONTRACT_NAME, data); 401 | } 402 | assert_player_count(account); 403 | 404 | const balance = getBalance(account); 405 | 406 | assert(balance[1] === 'FO', '只接受FO'); 407 | assert(balance[0] > 100, '请先进场,账号需要大于100FO'); 408 | 409 | // 锁定 410 | record.data.status = 2; 411 | record.data.buy_pay = pay; 412 | record.data.buyer = account; 413 | record.update(CONTRACT_NAME); 414 | } 415 | 416 | /** 417 | * @param id 订单id 418 | * @param pay 更改价格 419 | * 更新订单价格 420 | */ 421 | exports.updateorder = (id, price) => { 422 | const account = getAccount(); 423 | const { record } = getRecord(id); 424 | assert(record.data, error_msg[0]); 425 | assert(record.data.status == 1, error_msg[1]); 426 | if (record.data.type == 1 || record.data.type == 3){ 427 | assert(record.data.seller === account, '本人订单'); 428 | } else if (record.data.type == 2) { 429 | assert(record.data.buyer === account, '本人订单'); 430 | } else { 431 | assert(false, '订单错误'); 432 | } 433 | // 锁定 434 | record.data.price = price; 435 | record.update(CONTRACT_NAME); 436 | } 437 | 438 | 439 | function _after_cur_count (account) { 440 | const { player } = getPlayer(account); 441 | if (player.data) { 442 | // 当前参与数 443 | player.data.cur_count--; 444 | player.update(CONTRACT_NAME); 445 | } 446 | } 447 | 448 | /** 449 | * @param id 订单id 450 | * 卖家/买家取消订单 451 | */ 452 | exports.cancelorder = (id) => { 453 | const account = getAccount(); 454 | const { record } = getRecord(id); 455 | assert(record.data, error_msg[0]); 456 | if (record.data.seller === account) { 457 | // 卖家订单未锁定状态才能取消订单 458 | assert(record.data.status == 1, error_msg[1]); 459 | let pay = record.data.pay; 460 | // 进场收取服务费 461 | if (record.data.type == 3) { 462 | const pay_asset = record.data.pay.split(' '); 463 | const pay_length = pay_asset[0].split('.')[1].length; 464 | pay =(Number(pay_asset[0]) - getConfig(6) / 10000).toFixed(pay_length) + ' ' + pay_asset[1]; 465 | } 466 | // 需要减少cur_count todo 467 | _after_cur_count(account); 468 | sendToken(CONTRACT_NAME, account, pay, '退回订单pay'); 469 | record.remove(); 470 | } else if (record.data.buyer === account) { 471 | // 买家订单在锁定状态,取消订单 472 | if (record.data.type == 1) { 473 | // 买家只要确认该单没问题,无论是仲裁期间还是 474 | assert(record.data.status >= 2, error_msg[1]); 475 | // 需要减少cur_count todo 476 | _after_cur_count(account); 477 | record.data.status = 1; 478 | record.data.buyer = ''; 479 | record.update(CONTRACT_NAME); 480 | } else if (record.data.type == 2) { 481 | assert(record.data.status == 1, error_msg[1]); 482 | _after_cur_count(account); 483 | record.remove(); 484 | } else { 485 | assert(false, '其它订单不能取消'); 486 | } 487 | } else { 488 | assert(false, '非卖家/买家不能取消订单'); 489 | } 490 | // 删除改id所有仲裁记录 491 | delarbs(id); 492 | } 493 | 494 | 495 | /** 496 | * @param id 497 | * 卖家确认订单成功 498 | */ 499 | exports.result = (id) => { 500 | const account = getAccount(); 501 | const { record } = getRecord(id); 502 | assert(record.data, error_msg[0]); 503 | assert(record.data.seller === account, error_msg[2]); 504 | // 卖家可以在锁定/仲裁/上诉期间确定订单 505 | assert(record.data.status >= 2, error_msg[3]); 506 | 507 | const transfer_amount = record.data.buy_pay; 508 | const pay_asset = record.data.pay.split(' '); 509 | const pay_length = pay_asset[0].split('.')[1].length; 510 | const buy_pay_asset = record.data.buy_pay.split(' '); 511 | assert(pay_asset[1] === buy_pay_asset[1], 'buy === sell asset!'); 512 | const remain_pay = Number(pay_asset[0]) - Number(buy_pay_asset[0]); 513 | 514 | // 卖家确认 515 | const players = db.players(CONTRACT_NAME, CONTRACT_NAME); 516 | const buyer = players.find(record.data.buyer); 517 | if (buyer.data) { 518 | buyer.data.cur_count--; 519 | buyer.update(CONTRACT_NAME); 520 | } 521 | 522 | // 发送 523 | sendToken(CONTRACT_NAME, record.data.buyer, transfer_amount, 'pay success'); 524 | // 暂时用不到这个,现在每单都需要全额购买 525 | if (remain_pay > 0) { 526 | // 剩余继续 527 | record.data.pay = remain_pay.toFixed(pay_length) + ' ' + pay_asset[1]; 528 | record.data.buyer = ''; 529 | record.data.status = 1; 530 | record.update(CONTRACT_NAME); 531 | } else { 532 | record.remove(); 533 | } 534 | 535 | const player = players.find(account); 536 | if (player.data) { 537 | player.data.success_count++; 538 | player.update(CONTRACT_NAME); 539 | } 540 | // 删除改id所有仲裁记录 541 | delarbs(id); 542 | }; 543 | 544 | // 申请仲裁 545 | exports.applyarb = (id) => { 546 | const account = getAccount(); 547 | // 申请仲裁需要设置用户信息 548 | // assert_userinfo(account); 549 | const { record } = getRecord(id); 550 | assert(record.data, error_msg[0]); 551 | assert(account === record.data.seller || account === record.data.buyer, '需要买家或卖家'); 552 | assert(parseInt(record.data.dateline) + parseInt(getConfig(3)) < action.publication_time, '需要一天,双方没有和解才能,才能申请仲裁'); 553 | // 申请仲裁 554 | record.data.status = 3; 555 | record.data.dateline = action.publication_time; 556 | record.update(CONTRACT_NAME); 557 | } 558 | 559 | /** 560 | * @param id 561 | * @param win 1:卖家胜诉,2:买家胜诉 562 | * @param memo 胜诉备注 563 | * 单个bp调解胜诉 564 | * 卖家或买家不服可上诉, 565 | * 进入申述状态,只需要三个bp确认胜诉方,卖家可解锁订单或买家即可提现 566 | */ 567 | exports.arbitrate = (id, win, memo) => { 568 | const account = getAccount(); 569 | 570 | const blacklist = db.blacklist(CONTRACT_NAME, CONTRACT_NAME); 571 | const fuck_arbitrater = blacklist.find(account); 572 | // 禁止仲裁员黑名单 573 | assert(!fuck_arbitrater.data, error_msg[6]); 574 | 575 | assert(win == 1 || win == 2, error_msg[5]); 576 | 577 | const { record } = getRecord(id); 578 | assert(record.data, error_msg[0]); 579 | // assert(parseInt(record.data.dateline) + parseInt(getConfig(3)) < action.publication_time, '需要限定时间内,双方仍没有和解才能,仲裁员调查介入仲裁'); 580 | 581 | // bp 仲裁 582 | assert_arbitrate(account); 583 | 584 | const arbitration = db.arbitration(CONTRACT_NAME, CONTRACT_NAME, arbitrate_indexes); 585 | const cur_time = action.publication_time; 586 | let upper_itr = arbitration.indexes.record.find({record_id:id}); 587 | let count = 0; 588 | while (upper_itr.data) { 589 | assert(upper_itr.data.account !== account, "该账号,已仲裁"); 590 | count++; 591 | upper_itr = upper_itr.next(); 592 | } 593 | if (count > 0) { 594 | assert(record.data.status == 5, "当一个仲裁人裁决后, 如上诉,在有仲裁人加入时,订单状态必须为上诉仲裁中"); 595 | } 596 | // 如果是上诉中,则不需要设置仲裁中 597 | if (record.data.status == 3) { 598 | record.data.status = 4; 599 | } 600 | record.update(CONTRACT_NAME); 601 | arbitration.emplace(CONTRACT_NAME, { 602 | id: arbitration.get_primary_key(), 603 | record_id: id, 604 | account: account, 605 | dateline: cur_time, 606 | win: win, 607 | memo: memo 608 | }); 609 | } 610 | 611 | // 上诉 612 | exports.appeal = (id) => { 613 | const account = getAccount(); 614 | const { record } = getRecord(id); 615 | assert(record.data, error_msg[0]); 616 | assert(account === record.data.seller || account === record.data.buyer, '需要买家或卖家'); 617 | assert(record.data.status == 4, error_msg[3]); 618 | record.data.status = 5; 619 | record.data.dateline = action.publication_time; 620 | record.update(CONTRACT_NAME); 621 | } 622 | 623 | // 胜诉提现 624 | exports.withdraw = (id) => { 625 | const account = getAccount(); 626 | const { record } = getRecord(id); 627 | assert(record.data, error_msg[0]); 628 | assert(account === record.data.seller || account === record.data.buyer, '需要买家或卖家'); 629 | 630 | const arbitration = db.arbitration(CONTRACT_NAME, CONTRACT_NAME, arbitrate_indexes); 631 | let upper_itr = arbitration.indexes.record.find({record_id:id}); 632 | 633 | const cur_time = action.publication_time; 634 | if (record.data.status == 4) { 635 | // 仲裁和解 636 | assert(parseInt(record.data.dateline) + parseInt(getConfig(4)) < cur_time, '仲裁后,规定时间没有上诉,买家即可提现'); 637 | if (upper_itr.data) { 638 | if (upper_itr.data.win == 1) { 639 | // 卖家仲裁自动获胜,解除锁定 640 | // 卖家减少1 641 | _after_cur_count(record.data.buyer); 642 | record.data.status = 1; 643 | record.data.buyer = ''; 644 | record.update(CONTRACT_NAME); 645 | } else if (upper_itr.data.win == 2) { 646 | // 买家仲裁 647 | const players = db.players(CONTRACT_NAME, CONTRACT_NAME); 648 | const buyer = players.find(record.data.buyer); 649 | if (buyer.data) { 650 | buyer.data.cur_count--; 651 | buyer.update(CONTRACT_NAME); 652 | } 653 | const seller = players.find(record.data.seller); 654 | if (seller.data) { 655 | seller.data.cur_count--; 656 | seller.update(CONTRACT_NAME); 657 | } 658 | sendToken(CONTRACT_NAME, record.data.buyer, record.data.buy_pay, 'arbitrate success'); 659 | record.remove(); 660 | } 661 | upper_itr.remove(); 662 | } 663 | return; 664 | } 665 | assert(record.data.status == 5, '必须为上诉后'); 666 | assert(parseInt(record.data.dateline) + parseInt(getConfig(4)) < cur_time, '仲裁后,或者上诉后'); 667 | 668 | // 上诉后需要找限定bp数,裁定 669 | let seller_win = 0; 670 | let buyer_win = 0; 671 | let next_itr = upper_itr; 672 | while (next_itr.data && next_itr.data.record_id === id) { 673 | if (next_itr.data.win == 1) { 674 | seller_win ++; 675 | } else { 676 | buyer_win ++; 677 | } 678 | next_itr = next_itr.next(); 679 | } 680 | 681 | // 需要的仲裁数量 682 | const _arbitrater_count = getConfig(0); 683 | if (seller_win > buyer_win && seller_win >= _arbitrater_count) { 684 | // 胜诉解除锁定 685 | const players = db.players(CONTRACT_NAME, CONTRACT_NAME); 686 | // 买单当前记录减一 687 | const buyer = players.find(record.data.buyer); 688 | if (buyer.data) { 689 | // 当前参与数 690 | buyer.data.cur_count--; 691 | // 仲裁数加一 692 | buyer.data.arbitrate_count++; 693 | buyer.update(CONTRACT_NAME); 694 | } 695 | // 卖家胜诉,解除锁定 696 | record.data.status = 1; 697 | record.data.buyer = ''; 698 | record.update(CONTRACT_NAME); 699 | 700 | const seller = players.find(record.data.seller); 701 | if (seller.data) { 702 | // 当前参与数 703 | // seller.data.success_count++; 704 | // 仲裁数加一 705 | seller.data.arbitrate_count++; 706 | seller.update(CONTRACT_NAME); 707 | } 708 | } else if (buyer_win > seller_win && buyer_win >= _arbitrater_count) { 709 | // 买家胜诉 710 | const players = db.players(CONTRACT_NAME, CONTRACT_NAME); 711 | const buyer = players.find(record.data.buyer); 712 | if (buyer.data) { 713 | // 成功++ 714 | buyer.data.success_count++; 715 | // 仲裁数加一 716 | buyer.data.arbitrate_count++; 717 | buyer.update(CONTRACT_NAME); 718 | } 719 | const seller = players.find(record.data.seller); 720 | if (seller.data) { 721 | // 仲裁数加一 722 | seller.data.cur_count--; 723 | seller.data.arbitrate_count++; 724 | seller.update(CONTRACT_NAME); 725 | } 726 | 727 | sendToken(CONTRACT_NAME, record.data.buyer, record.data.buy_pay, 'appeal success'); 728 | // 删除订单作为惩罚 729 | record.remove(); 730 | } else { 731 | assert(false, '还不能提现'); 732 | } 733 | while (upper_itr.data) { 734 | next_itr = upper_itr.next(); 735 | upper_itr.remove(); 736 | upper_itr = next_itr; 737 | } 738 | } 739 | 740 | /** 741 | * @param type 1:微信群二维码... 742 | * @param acct 账号 743 | * @param ref 推荐人 744 | * 设置参与仲裁群 745 | */ 746 | exports.setgroup = (acct) => { 747 | const account = getAccount(); 748 | assert_arbitrate(account); 749 | const groups = db.arbgroups(CONTRACT_NAME, CONTRACT_NAME); 750 | const arbgroup = groups.find(account); 751 | 752 | if (arbgroup.data) { 753 | arbgroup.data.acct = acct; 754 | arbgroup.update(account); 755 | } else { 756 | groups.emplace(CONTRACT_NAME, { 757 | account, 758 | acct: acct 759 | }); 760 | } 761 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
明天上线,敬请期待
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "deotc", 3 | "version": "0.2.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "axios": "^0.21.1", 12 | "bootstrap": "^4.3.1", 13 | "bootstrap-vue": "^2.21.2", 14 | "fibos.js": "^0.3.6", 15 | "js-base64": "^2.5.1", 16 | "moment": "^2.24.0", 17 | "url-loader": "^4.1.0", 18 | "vue": "^2.6.6", 19 | "vue-clipboard2": "^0.3.0", 20 | "vue-i18n": "^8.9.0", 21 | "vue-qrcode-reader": "^2.0.4", 22 | "vue-qriously": "^1.1.1", 23 | "vue-router": "^3.0.2" 24 | }, 25 | "devDependencies": { 26 | "@vue/cli-plugin-babel": "^3.5.0", 27 | "@vue/cli-plugin-eslint": "^3.5.0", 28 | "@vue/cli-service": "^4.5.11", 29 | "babel-eslint": "^10.0.1", 30 | "eslint": "^5.8.0", 31 | "eslint-plugin-vue": "^5.0.0", 32 | "vue-template-compiler": "^2.5.21" 33 | }, 34 | "eslintConfig": { 35 | "root": true, 36 | "env": { 37 | "node": true 38 | }, 39 | "extends": [ 40 | "plugin:vue/essential", 41 | "eslint:recommended" 42 | ], 43 | "rules": { 44 | "no-console": "off" 45 | }, 46 | "parserOptions": { 47 | "parser": "babel-eslint" 48 | } 49 | }, 50 | "postcss": { 51 | "plugins": { 52 | "autoprefixer": {} 53 | } 54 | }, 55 | "browserslist": [ 56 | "> 1%", 57 | "last 2 versions", 58 | "not ie <= 8" 59 | ], 60 | "resolutions": { 61 | "fibos.js/eosjs/eosjs-api": "git+https://git@github.com/kilmas/eosjs-api.git" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kilmas/deotc/855a60798677a290515973f77035a65003a318b7/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 21 | 22 | 24 | -------------------------------------------------------------------------------- /src/assets/alipay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kilmas/deotc/855a60798677a290515973f77035a65003a318b7/src/assets/alipay.png -------------------------------------------------------------------------------- /src/assets/bitgxc.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kilmas/deotc/855a60798677a290515973f77035a65003a318b7/src/assets/bitgxc.jpg -------------------------------------------------------------------------------- /src/assets/group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kilmas/deotc/855a60798677a290515973f77035a65003a318b7/src/assets/group.png -------------------------------------------------------------------------------- /src/assets/group2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kilmas/deotc/855a60798677a290515973f77035a65003a318b7/src/assets/group2.png -------------------------------------------------------------------------------- /src/assets/guess.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kilmas/deotc/855a60798677a290515973f77035a65003a318b7/src/assets/guess.ico -------------------------------------------------------------------------------- /src/assets/wechat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kilmas/deotc/855a60798677a290515973f77035a65003a318b7/src/assets/wechat.png -------------------------------------------------------------------------------- /src/components/Otc.vue: -------------------------------------------------------------------------------- 1 | 960 | 961 | 1909 | 1910 | 1911 | -------------------------------------------------------------------------------- /src/i18n/i18n.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueI18n from 'vue-i18n' 3 | import messages from './langs' 4 | Vue.use(VueI18n) 5 | // 从localStorage中拿到用户的语言选择,如果没有,那默认中文。 6 | const i18n = new VueI18n({ 7 | locale: localStorage.lang || 'cn', 8 | messages, 9 | }) 10 | // locale.i18n((key, value) => i18n.t(key, value)) //为了实现element插件的多语言切换 11 | export default i18n -------------------------------------------------------------------------------- /src/i18n/langs/cn.js: -------------------------------------------------------------------------------- 1 | const cn = { 2 | otcRules: [ 3 | "布卖单或买单,需要该账号有小量FO TOKEN,如账号无FO,可进小额进场,购置小量FO,小额进场无申诉.", 4 | "卖家布置卖单,账号需超过一定量的FO,卖家提供卖TOKEN数量和单价,并把该TOKEN数量,抵押到合约当中锁定.", 5 | "买家点击购买订单,购买即锁定,双方自助联系沟通.", 6 | "买家法币转账,然后卖方解锁该订单TOKEN,并由合约解锁TOKEN并转给买家,完成交易.", 7 | "订单锁定过程中,双方出现异议,可交由社区仲裁,仲裁员由社区节点或大户账号均可参与调解仲裁.", 8 | "锁定订单后一天,买卖任一方均可提出仲裁,仲裁员判断一方胜诉,双方无异议,沟通和解,胜诉方可提现或解锁该订单.", 9 | "一名仲裁员仲裁调解后,买卖一方如仍由异议,均可在一天内提出上诉.", 10 | "上诉状态中的订单,需要三个以上仲裁员获胜票数并且胜诉方的得票数比败诉方多,才能认定获胜", 11 | "获胜买方可以自助提现,获胜卖方可以解锁该订单,继续放置卖单", 12 | "布小额进场卖单,需要账号有大量FO,并需抵押1000 FO布单,取消并解锁订单,合约扣留1FO作为为资源抵扣", 13 | "布置卖单需要设置个人收款信息,推荐使用微信群二维码,提供给买家联系", 14 | "该OTC,为OTC协议,纯属去中心化模式,无第三方经营,上线即跑路", 15 | ], 16 | fair: '公平', 17 | allRecord: '所有记录' 18 | } 19 | 20 | export default cn; -------------------------------------------------------------------------------- /src/i18n/langs/en.js: -------------------------------------------------------------------------------- 1 | const en = { 2 | otcRules: [], 3 | fair: 'FAIR', 4 | allRecord: 'ALLRECORD' 5 | } 6 | 7 | export default en; -------------------------------------------------------------------------------- /src/i18n/langs/index.js: -------------------------------------------------------------------------------- 1 | import en from './en'; 2 | import cn from './cn'; 3 | export default { 4 | en: en, 5 | cn: cn 6 | } -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | import i18n from './i18n/i18n' 4 | 5 | import "bootstrap/dist/css/bootstrap.min.css" 6 | import "bootstrap-vue/dist/bootstrap-vue.css" 7 | 8 | import BootstrapVue from "bootstrap-vue" 9 | Vue.use(BootstrapVue) 10 | import router from './router' 11 | import App from './App.vue' 12 | 13 | new Vue({ 14 | el: '#app', 15 | i18n, 16 | render: h => h(App), 17 | router 18 | }) 19 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | 4 | const Otc = () => import('../components/Otc.vue') 5 | 6 | Vue.use(Router) 7 | 8 | export default new Router({ 9 | mode: 'history', 10 | scrollBehavior: () => ({ y: 0 }), 11 | linkActiveClass: 'is-active', 12 | routes: [ 13 | { 14 | path: '/', 15 | components: { 16 | default: Otc 17 | } 18 | } 19 | ] 20 | }) 21 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | // vue.config.js 2 | module.exports = { 3 | // 选项... 4 | productionSourceMap: false, 5 | devServer: {} 6 | } --------------------------------------------------------------------------------