Page MenuHomePhabricator

[Cashtab + token-server + ecash-coinselect] Implement token rewards for Cashtab users
ClosedPublic

Authored by bytesofman on Apr 16 2024, 22:16.

Details

Summary

T2838

Implement claimable token rewards for users of Cashtab. A valid ecash address may claim a reward every 24 hours.

Summary of changes to get this feature

  • In Cashtab, we add a new "Rewards" screen. The screen displays a button "Claim Rewards" if the user is eligible (no claimed rewards for 24 hours). If ineligible, a countdown is displayed to when eligibility returns.
  • In token-server, we add the logic to build and broadcast a token reward. We add an endpoint to summon a token reward tx to an address, if it is eligible.
  • In ecash-coinselect, we add stub type declarations, so typescript applications (like token-server) can still build if they are using it.

I have tested this locally. Able to claim rewards, see countdown for ineligible address, get expected error msg if the server is out of money. It is tricky to test the full stack locally. You will need to create your own token, change URL in cashtab, change wallet settings in token-server. If you really want to do this for hobbyist reasons, ping me on Telegram. Otherwise, it is probably best to land this when we will both be available for at least an hour.

DM me with any concerns about DOSing or draining the rewards faucet. Since the cost of token rewards is relatively low (we can test more than 10,000 rewards for less than $10), I am in favor of launching, then dealing with "attacks" as they emerge.

Test Plan

npm test

Diff Detail

Repository
rABC Bitcoin ABC
Branch
token-rewards
Lint
Lint Passed
Unit
No Test Coverage
Build Status
Buildable 28626
Build 56793: Build Difftoken-server-tests · cashtab-tests · ecash-coinselect-tests
Build 56792: arc lint + arc unit

Event Timeline

Tail of the build log:

  vectors.js                     |     100 |      100 |     100 |     100 |                                                                                                          
 src/slpv1                       |      96 |    90.16 |     100 |      96 |                                                                                                          
  index.js                       |      96 |    90.16 |     100 |      96 | 269,280,332,336,341                                                                                      
 src/slpv1/fixtures              |     100 |      100 |     100 |     100 |                                                                                                          
  mocks.js                       |     100 |      100 |     100 |     100 |                                                                                                          
  vectors.js                     |     100 |      100 |     100 |     100 |                                                                                                          
 src/transactions                |     100 |      100 |     100 |     100 |                                                                                                          
  index.js                       |     100 |      100 |     100 |     100 |                                                                                                          
 src/transactions/fixtures       |     100 |      100 |     100 |     100 |                                                                                                          
  mocks.js                       |     100 |      100 |     100 |     100 |                                                                                                          
  vectors.js                     |     100 |      100 |     100 |     100 |                                                                                                          
 src/utils                       |   92.06 |       92 |     100 |   92.06 |                                                                                                          
  cashMethods.js                 |     100 |      100 |     100 |     100 |                                                                                                          
  formatting.js                  |   91.07 |    90.47 |     100 |   91.07 | 20,30,53-55                                                                                              
 src/utils/fixtures              |       0 |        0 |       0 |       0 |                                                                                                          
  vectors.js                     |       0 |        0 |       0 |       0 |                                                                                                          
 src/validation                  |   96.09 |    97.18 |     100 |   96.38 |                                                                                                          
  index.js                       |   96.09 |    97.18 |     100 |   96.38 | 118-119,261,293,313,360,558,654-655,789,794                                                              
 src/validation/fixtures         |     100 |      100 |     100 |     100 |                                                                                                          
  mocks.js                       |     100 |      100 |     100 |     100 |                                                                                                          
  vectors.js                     |     100 |      100 |     100 |     100 |                                                                                                          
 src/wallet                      |   94.54 |    86.08 |   87.03 |   94.96 |                                                                                                          
  context.js                     |     100 |      100 |     100 |     100 |                                                                                                          
  index.js                       |     100 |      100 |     100 |     100 |                                                                                                          
  useWallet.js                   |   92.08 |    80.57 |   79.41 |    92.7 | 85,169,442-448,485,555,626,654,691-692,701-714,770,801,807-810,878,944-945,972                           
 src/wallet/fixtures             |     100 |      100 |     100 |     100 |                                                                                                          
  mocks.js                       |     100 |      100 |     100 |     100 |                                                                                                          
  vectors.js                     |     100 |      100 |     100 |     100 |                                                                                                          
---------------------------------|---------|----------|---------|---------|----------------------------------------------------------------------------------------------------------

##teamcity[blockOpened name='Code Coverage Summary']
##teamcity[buildStatisticValue key='CodeCoverageAbsBCovered' value='3527']
##teamcity[buildStatisticValue key='CodeCoverageAbsBTotal' value='4300']
##teamcity[buildStatisticValue key='CodeCoverageAbsRCovered' value='1778']
##teamcity[buildStatisticValue key='CodeCoverageAbsRTotal' value='2276']
##teamcity[buildStatisticValue key='CodeCoverageAbsMCovered' value='697']
##teamcity[buildStatisticValue key='CodeCoverageAbsMTotal' value='933']
##teamcity[buildStatisticValue key='CodeCoverageAbsLCovered' value='3456']
##teamcity[buildStatisticValue key='CodeCoverageAbsLTotal' value='4207']
##teamcity[blockClosed name='Code Coverage Summary']

Summary of all failing tests
FAIL src/components/Rewards/__tests__/index.test.js
  ● Test suite failed to run

    Your test suite must contain at least one test.

      at onResult (node_modules/@jest/core/build/TestScheduler.js:133:18)
      at node_modules/@jest/core/build/TestScheduler.js:254:19
      at node_modules/emittery/index.js:363:13
          at Array.map (<anonymous>)
      at Emittery.emit (node_modules/emittery/index.js:361:23)


Test Suites: 1 failed, 33 passed, 34 total
Tests:       792 passed, 792 total
Snapshots:   0 total
Time:        35.231 s
Ran all test suites.
Build cashtab-tests failed with exit code 1

Add reward tx broadcasting function to token server, add stub api endpoint for sending rwd tx to token-server, add types to ecash-coinselect

Tail of the build log:

Test depends on mock-chronik-client. Installing mock-chronik-client dependencies...
/work/modules/mock-chronik-client /work/abc-ci-builds/token-server-tests

added 236 packages, and audited 237 packages in 1s

35 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
Test does not depend on ecash-lib-wasm, skipping
/work/apps/token-server /work/modules/mock-chronik-client /work/abc-ci-builds/token-server-tests
npm WARN deprecated har-validator@5.1.5: this library is no longer supported
npm WARN deprecated uuid@3.4.0: Please upgrade  to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.
npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142

added 644 packages, and audited 646 packages in 6s

114 packages are looking for funding
  run `npm fund` for details

7 moderate severity vulnerabilities

To address issues that do not require attention, run:
  npm audit fix

To address all issues (including breaking changes), run:
  npm audit fix --force

Run `npm audit` for details.
CI configured to test build. Building...

> token-server@0.0.0 prebuild
> ts-node scripts/prepSecrets.ts

secrets.ts does not exist, copying secrets.sample.ts...

> token-server@0.0.0 build
> tsc

src/routes.ts(206,26): error TS2339: Property 'becomesEligible' does not exist on type 'SendRewardResponse'.
src/routes.ts(209,26): error TS2339: Property 'isEligible' does not exist on type 'SendRewardResponse'.
Build token-server-tests failed with exit code 2

add functions and tests to token server, still need to add tests for the new rewards route

testing the /claim/ endpoint, deprecating serverOutputScript in config

ecash-coinselect version bump and readme update

working locally (still need to change token-server and cashtab settings back to prod)

Tail of the build log:

> token-server@0.0.0 build
> tsc


> token-server@0.0.0 pretest
> ts-node scripts/prepSecrets.ts

secrets.ts exists, proceeding to build...

> token-server@0.0.0 test
> mocha --reporter mocha-junit-reporter --reporter-options mochaFile=test_results/token-server-junit.xml --reporter-options testsuitesTitle=Token Server Unit Tests --reporter-options rootSuiteTitle=Token Server


TSError: ⨯ Unable to compile TypeScript:
test/routes.test.ts(50,45): error TS2339: Property 'serverOutputScript' does not exist on type 'TokenServerConfig'.

    at createTSError (/work/apps/token-server/node_modules/ts-node/src/index.ts:859:12)
    at reportTSError (/work/apps/token-server/node_modules/ts-node/src/index.ts:863:19)
    at getOutput (/work/apps/token-server/node_modules/ts-node/src/index.ts:1077:36)
    at Object.compile (/work/apps/token-server/node_modules/ts-node/src/index.ts:1433:41)
    at Module.m._compile (/work/apps/token-server/node_modules/ts-node/src/index.ts:1617:30)
    at module.exports (/usr/lib/node_modules/nyc/node_modules/default-require-extensions/js.js:7:9)
    at /usr/lib/node_modules/nyc/node_modules/append-transform/index.js:64:4
    at require.extensions.<computed> (/work/apps/token-server/node_modules/ts-node/src/index.ts:1621:12)
    at Object.<anonymous> (/usr/lib/node_modules/nyc/node_modules/append-transform/index.js:64:4)
    at Module.load (node:internal/modules/cjs/loader:1206:32)
    at Function.Module._load (node:internal/modules/cjs/loader:1022:12)
    at Module.require (node:internal/modules/cjs/loader:1231:19)
    at require (node:internal/modules/helpers:179:18)
    at Object.exports.requireOrImport (/work/apps/token-server/node_modules/mocha/lib/nodejs/esm-utils.js:53:16)
    at async Object.exports.loadFilesAsync (/work/apps/token-server/node_modules/mocha/lib/nodejs/esm-utils.js:100:20)
    at async singleRun (/work/apps/token-server/node_modules/mocha/lib/cli/run-helpers.js:125:3)
    at async Object.exports.handler (/work/apps/token-server/node_modules/mocha/lib/cli/run.js:370:5)
-------------------|---------|----------|---------|---------|-------------------
File               | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------------|---------|----------|---------|---------|-------------------
All files          |   23.43 |     3.57 |       0 |   21.31 |                   
 scripts           |      60 |       50 |     100 |      60 |                   
  prepSecrets.ts   |      60 |       50 |     100 |      60 | 17-18             
 src               |      20 |        0 |       0 |      20 |                   
  rewards.ts       |      20 |        0 |       0 |      20 | 38-60             
 src/chronik       |    20.4 |        0 |       0 |   17.39 |                   
  clientHandler.ts |   11.76 |        0 |       0 |    12.5 | 31-89             
  parse.ts         |   18.18 |        0 |       0 |   18.18 | 26-32,47-69,78-93 
  wsHandler.ts     |      40 |      100 |       0 |      25 | 23-33             
-------------------|---------|----------|---------|---------|-------------------

##teamcity[blockOpened name='Code Coverage Summary']
##teamcity[buildStatisticValue key='CodeCoverageAbsBCovered' value='15']
##teamcity[buildStatisticValue key='CodeCoverageAbsBTotal' value='64']
##teamcity[buildStatisticValue key='CodeCoverageAbsRCovered' value='1']
##teamcity[buildStatisticValue key='CodeCoverageAbsRTotal' value='28']
##teamcity[buildStatisticValue key='CodeCoverageAbsMCovered' value='0']
##teamcity[buildStatisticValue key='CodeCoverageAbsMTotal' value='10']
##teamcity[buildStatisticValue key='CodeCoverageAbsLCovered' value='13']
##teamcity[buildStatisticValue key='CodeCoverageAbsLTotal' value='61']
##teamcity[blockClosed name='Code Coverage Summary']
mv: cannot stat 'test_results/token-server-junit.xml': No such file or directory
Build token-server-tests failed with exit code 1

revert from local testing to prod settings, check eligibility on address change in rewards screen

Tail of the build log:

> token-server@0.0.0 build
> tsc


> token-server@0.0.0 pretest
> ts-node scripts/prepSecrets.ts

secrets.ts exists, proceeding to build...

> token-server@0.0.0 test
> mocha --reporter mocha-junit-reporter --reporter-options mochaFile=test_results/token-server-junit.xml --reporter-options testsuitesTitle=Token Server Unit Tests --reporter-options rootSuiteTitle=Token Server


TSError: ⨯ Unable to compile TypeScript:
test/routes.test.ts(50,45): error TS2339: Property 'serverOutputScript' does not exist on type 'TokenServerConfig'.

    at createTSError (/work/apps/token-server/node_modules/ts-node/src/index.ts:859:12)
    at reportTSError (/work/apps/token-server/node_modules/ts-node/src/index.ts:863:19)
    at getOutput (/work/apps/token-server/node_modules/ts-node/src/index.ts:1077:36)
    at Object.compile (/work/apps/token-server/node_modules/ts-node/src/index.ts:1433:41)
    at Module.m._compile (/work/apps/token-server/node_modules/ts-node/src/index.ts:1617:30)
    at module.exports (/usr/lib/node_modules/nyc/node_modules/default-require-extensions/js.js:7:9)
    at /usr/lib/node_modules/nyc/node_modules/append-transform/index.js:64:4
    at require.extensions.<computed> (/work/apps/token-server/node_modules/ts-node/src/index.ts:1621:12)
    at Object.<anonymous> (/usr/lib/node_modules/nyc/node_modules/append-transform/index.js:64:4)
    at Module.load (node:internal/modules/cjs/loader:1206:32)
    at Function.Module._load (node:internal/modules/cjs/loader:1022:12)
    at Module.require (node:internal/modules/cjs/loader:1231:19)
    at require (node:internal/modules/helpers:179:18)
    at Object.exports.requireOrImport (/work/apps/token-server/node_modules/mocha/lib/nodejs/esm-utils.js:53:16)
    at async Object.exports.loadFilesAsync (/work/apps/token-server/node_modules/mocha/lib/nodejs/esm-utils.js:100:20)
    at async singleRun (/work/apps/token-server/node_modules/mocha/lib/cli/run-helpers.js:125:3)
    at async Object.exports.handler (/work/apps/token-server/node_modules/mocha/lib/cli/run.js:370:5)
-------------------|---------|----------|---------|---------|-------------------
File               | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------------|---------|----------|---------|---------|-------------------
All files          |   23.43 |     3.57 |       0 |   21.31 |                   
 scripts           |      60 |       50 |     100 |      60 |                   
  prepSecrets.ts   |      60 |       50 |     100 |      60 | 17-18             
 src               |      20 |        0 |       0 |      20 |                   
  rewards.ts       |      20 |        0 |       0 |      20 | 38-60             
 src/chronik       |    20.4 |        0 |       0 |   17.39 |                   
  clientHandler.ts |   11.76 |        0 |       0 |    12.5 | 31-89             
  parse.ts         |   18.18 |        0 |       0 |   18.18 | 26-32,47-69,78-93 
  wsHandler.ts     |      40 |      100 |       0 |      25 | 23-33             
-------------------|---------|----------|---------|---------|-------------------

##teamcity[blockOpened name='Code Coverage Summary']
##teamcity[buildStatisticValue key='CodeCoverageAbsBCovered' value='15']
##teamcity[buildStatisticValue key='CodeCoverageAbsBTotal' value='64']
##teamcity[buildStatisticValue key='CodeCoverageAbsRCovered' value='1']
##teamcity[buildStatisticValue key='CodeCoverageAbsRTotal' value='28']
##teamcity[buildStatisticValue key='CodeCoverageAbsMCovered' value='0']
##teamcity[buildStatisticValue key='CodeCoverageAbsMTotal' value='10']
##teamcity[buildStatisticValue key='CodeCoverageAbsLCovered' value='13']
##teamcity[buildStatisticValue key='CodeCoverageAbsLTotal' value='61']
##teamcity[blockClosed name='Code Coverage Summary']
mv: cannot stat 'test_results/token-server-junit.xml': No such file or directory
Build token-server-tests failed with exit code 1

remove debug comments, remove unused imports

bytesofman edited the summary of this revision. (Show Details)
bytesofman retitled this revision from [Cashtab + token-server] Implement token rewards for Cashtab users to [Cashtab + token-server + ecash-coinselect] Implement token rewards for Cashtab users and support typescript apps in ecash-coinselect.
bytesofman retitled this revision from [Cashtab + token-server + ecash-coinselect] Implement token rewards for Cashtab users and support typescript apps in ecash-coinselect to [Cashtab + token-server + ecash-coinselect] Implement token rewards for Cashtab users.
bytesofman added inline comments.
apps/token-server/config.ts
10 ↗(On Diff #47258)

we used to specify this as a config param. You need it specified as a constant somewhere so the server can reference it when determining if an address is eligible for rewards or not. It can't be a param to simplify testing because the testing occurs on an API endpoint.

To make errors less likely, we should get this from the server's address directly. So, instead of keeping it in config, now we derive it from secrets

apps/token-server/package.json
57 ↗(On Diff #47258)

We install this locally.

Going forward, all our prod apps should use monorepo version of dependencies and not the npm version. See T3548 for rationale.

With this one -- its only dependencies are dev dependencies. So, we do not need to modify CI (as we did for mock-chronik-client).

We may have to modify CI in the future, but also it is looking like ecash-coinselect should be replaced by ecash-lib. Either way, no need now.

apps/token-server/secrets.sample.ts
37 ↗(On Diff #47258)

The unit tests run with this wallet. So, if you change it, the expected rawtxs will be different.

apps/token-server/src/transactions.ts
54 ↗(On Diff #47258)

necessary to not throw an error on utxos without a token key

emack requested changes to this revision.Apr 18 2024, 03:08
emack added a subscriber: emack.
emack added inline comments.
apps/token-server/src/transactions.ts
157 ↗(On Diff #47259)

The code above looks like this is a standard slp tx. Should bake in a prefix for these reward transactions. As per other comment, this will make parsing of rewards history much easier, especially once that lookupByLokadId function makes it into ecash lib.

cashtab/src/components/Rewards/index.js
141–157 ↗(On Diff #47259)

Since this is the primary user facing interface for rewards it needs to have a lot more context than just a simple button.
e.g.

  • How it works (issuing of token to cashtab addresses)
  • Eligibility rules for rewards
  • History of claims for this address?

This will avoid potential confusion with avalanche staking rewards.

This revision now requires changes to proceed.Apr 18 2024, 03:08
bytesofman marked an inline comment as done.
bytesofman added inline comments.
apps/token-server/src/transactions.ts
157 ↗(On Diff #47259)

unfortunately this is impossible as slp1 requires the entire opreturn field.

other version of SLP, or, say, ALP -- could do this since ALP uses EMPP

but, straightforward enough to parse rewards history. can look up txs by token id, then can filter those down to the ones sent by this output script.

token-server already does this to determine eligibility

cashtab/src/components/Rewards/index.js
141–157 ↗(On Diff #47259)

maybe. I don't think we need to launch with that though.

already a lot of engineering here. depending on how this is used, might get rid of the feature entirely, or pivot it to something else. who knows.

imo we must always avoid launching something for concerns (not related to security) about hypothetical user preferences. we get that information by launching.

Accepted post TG chat

This revision is now accepted and ready to land.Apr 18 2024, 05:42