Skip to content
This repository was archived by the owner on Dec 21, 2021. It is now read-only.

Commit 108afd3

Browse files
author
jtakalai
authored
Merge pull request #239 from streamr-dev/ETH-81-expose-signature-transport
ETH-81: Expose transportSignatures for an alternative withdraw flow
2 parents 007ee21 + c0105ca commit 108afd3

File tree

7 files changed

+361
-216
lines changed

7 files changed

+361
-216
lines changed

README.md

Lines changed: 51 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -360,11 +360,11 @@ const dataUnion = client.getDataUnion(dataUnionAddress)
360360
<!-- This stuff REALLY isn't for those who use our infrastructure, neither DU admins nor DU client devs. It's only relevant if you're setting up your own sidechain.
361361
These DataUnion-specific options can be given to `new StreamrClient` options:
362362
363-
| Property | Default | Description |
364-
| :---------------------------------- | :----------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------- |
365-
| dataUnion.minimumWithdrawTokenWei | 1000000 | Threshold value set in AMB configs, smallest token amount that can pass over the bridge |
366-
| dataUnion.freeWithdraw | false | true = someone else pays for the gas when transporting the withdraw tx to mainnet |
367-
| | | false = client does the transport as self-service and pays the mainnet gas costs |
363+
| Property | Default | Description |
364+
| :---------------------------------- | :----------------------------------------------------- | :----------------------------------------------------------------------------------------- |
365+
| dataUnion.minimumWithdrawTokenWei | 1000000 | Threshold value set in AMB configs, smallest token amount that can pass over the bridge |
366+
| dataUnion.payForTransport | true | true = client does the transport as self-service and pays the mainnet gas costs |
367+
| | | false = someone else pays for the gas when transporting the withdraw tx to mainnet |
368368
-->
369369

370370
### Admin Functions
@@ -375,8 +375,10 @@ These DataUnion-specific options can be given to `new StreamrClient` options:
375375
| setAdminFee(newFeeFraction) | Transaction receipt | `newFeeFraction` is a `Number` between 0.0 and 1.0 (inclusive) |
376376
| addMembers(memberAddressList) | Transaction receipt | Add members |
377377
| removeMembers(memberAddressList) | Transaction receipt | Remove members from Data Union |
378-
| withdrawAllToMember(memberAddress\[, [options](#withdraw-options)\]) | Transaction receipt | Send all withdrawable earnings to the member's address |
379-
| withdrawAllToSigned(memberAddress, recipientAddress, signature\[, [options](#withdraw-options)\]) | Transaction receipt | Send all withdrawable earnings to the address signed off by the member (see [example below](#member-functions)) |
378+
| withdrawAllToMember(memberAddress\[, [options](#withdraw-options)\]) | Transaction receipt `*` | Send all withdrawable earnings to the member's address |
379+
| withdrawAllToSigned(memberAddress, recipientAddress, signature\[, [options](#withdraw-options)\]) | Transaction receipt `*` | Send all withdrawable earnings to the address signed off by the member (see [example below](#member-functions)) |
380+
381+
`*` The return value type may vary depending on [the given options](#withdraw-options) that describe the use case.
380382

381383
Here's how to deploy a Data Union contract with 30% Admin fee and add some members:
382384

@@ -399,14 +401,17 @@ const receipt = await dataUnion.addMembers([
399401

400402
### Member functions
401403

402-
| Name | Returns | Description |
403-
| :---------------------------------------------------------------- | :------------------ | :-------------------------------------------------------------------------- |
404-
| join(\[secret]) | JoinRequest | Join the Data Union (if a valid secret is given, the promise waits until the automatic join request has been processed) |
405-
| isMember(memberAddress) | boolean | |
406-
| withdrawAll(\[[options](#withdraw-options)\]) | Transaction receipt | Withdraw funds from Data Union |
407-
| withdrawAllTo(recipientAddress\[, [options](#withdraw-options)\]) | Transaction receipt | Donate/move your earnings to recipientAddress instead of your memberAddress |
408-
| signWithdrawAllTo(recipientAddress) | Signature (string) | Signature that can be used to withdraw all available tokens to given recipientAddress |
409-
| signWithdrawAmountTo(recipientAddress, amountTokenWei) | Signature (string) | Signature that can be used to withdraw a specific amount of tokens to given recipientAddress |
404+
| Name | Returns | Description |
405+
| :-------------------------------------------------------------------- | :------------------------ | :-------------------------------------------------------------------------- |
406+
| join(\[secret]) | JoinRequest | Join the Data Union (if a valid secret is given, the promise waits until the automatic join request has been processed) |
407+
| isMember(memberAddress) | boolean | |
408+
| withdrawAll(\[[options](#withdraw-options)\]) | Transaction receipt `*` | Withdraw funds from Data Union |
409+
| withdrawAllTo(recipientAddress\[, [options](#withdraw-options)\]) | Transaction receipt `*` | Donate/move your earnings to recipientAddress instead of your memberAddress |
410+
| signWithdrawAllTo(recipientAddress) | Signature (string) | Signature that can be used to withdraw all available tokens to given recipientAddress |
411+
| signWithdrawAmountTo(recipientAddress, amountTokenWei) | Signature (string) | Signature that can be used to withdraw a specific amount of tokens to given recipientAddress |
412+
| transportMessage(messageHash[, pollingIntervalMs[, retryTimeoutMs]]) | Transaction receipt | Send the mainnet transaction to withdraw tokens from the sidechain |
413+
414+
`*` The return value type may vary depending on [the given options](#withdraw-options) that describe the use case.
410415

411416
Here's an example on how to sign off on a withdraw to (any) recipientAddress (NOTE: this requires no gas!)
412417

@@ -434,6 +439,15 @@ const dataUnion = client.getDataUnion(dataUnionAddress)
434439
const receipt = await dataUnion.withdrawAllToSigned(memberAddress, recipientAddress, signature)
435440
```
436441

442+
The `messageHash` argument to `transportMessage` will come from the withdraw function with the specific options. The following is equivalent to the above withdraw line:
443+
```js
444+
const messageHash = await dataUnion.withdrawAllToSigned(memberAddress, recipientAddress, signature, {
445+
payForTransport: false,
446+
waitUntilTransportIsComplete: false,
447+
}) // only pay for sidechain gas
448+
const receipt = await dataUnion.transportMessage(messageHash) // only pay for mainnet gas
449+
```
450+
437451
### Query functions
438452

439453
These are available for everyone and anyone, to query publicly available info from a Data Union:
@@ -460,13 +474,29 @@ const withdrawableWei = await dataUnion.getWithdrawableEarnings(memberAddress)
460474

461475
The functions `withdrawAll`, `withdrawAllTo`, `withdrawAllToMember`, `withdrawAllToSigned` all can take an extra "options" argument. It's an object that can contain the following parameters:
462476

463-
| Name | Default | Description |
464-
| :---------------- | :-------------------- | :---------------------------------------------------------------------------------- |
465-
| sendToMainnet | true | Whether to send the withdrawn DATA tokens to mainnet address (or sidechain address) |
466-
| pollingIntervalMs | 1000 (1&nbsp;second) | How often requests are sent to find out if the withdraw has completed |
467-
| retryTimeoutMs | 60000 (1&nbsp;minute) | When to give up when waiting for the withdraw to complete |
477+
| Name | Default | Description |
478+
| :---------------- | :-------------------- | :---------------------------------------------------------------------------------- |
479+
| sendToMainnet | true | Whether to send the withdrawn DATA tokens to mainnet address (or sidechain address) |
480+
| payForTransport | true | Whether to pay for the withdraw transaction signature transport to mainnet over the bridge |
481+
| waitUntilTransportIsComplete | true | Whether to wait until the withdrawn DATA tokens are visible in mainnet |
482+
| pollingIntervalMs | 1000 (1&nbsp;second) | How often requests are sent to find out if the withdraw has completed |
483+
| retryTimeoutMs | 60000 (1&nbsp;minute) | When to give up when waiting for the withdraw to complete |
484+
485+
These withdraw transactions are sent to the sidechain, so gas price shouldn't be manually set (fees will hopefully stay very low),
486+
but a little bit of [sidechain native token](https://www.xdaichain.com/for-users/get-xdai-tokens) is nonetheless required.
487+
488+
The return values from the withdraw functions also depend on the options.
489+
490+
If `sendToMainnet: false`, other options don't apply at all, and **sidechain transaction receipt** is returned as soon as the withdraw transaction is done. This should be fairly quick in the sidechain.
491+
492+
The use cases corresponding to the different combinations of the boolean flags:
468493

469-
These withdraw transactions are sent to the sidechain, so gas price shouldn't be manually set (fees will hopefully stay very low), but a little bit of [sidechain native token](https://www.xdaichain.com/for-users/get-xdai-tokens) is nonetheless required.
494+
| `transport` | `wait` | Returns | Effect |
495+
| :---------- | :------ | :------ | :----- |
496+
| `true` | `true` | Transaction receipt | *(default)* Self-service bridge to mainnet, client pays for mainnet gas |
497+
| `true` | `false` | Transaction receipt | Self-service bridge to mainnet (but **skip** the wait that double-checks the withdraw succeeded and tokens arrived to destination) |
498+
| `false` | `true` | `null` | Someone else pays for the mainnet gas automatically, e.g. the bridge operator (in this case the transaction receipt can't be returned) |
499+
| `false` | `false` | AMB message hash | Someone else pays for the mainnet gas, but we need to give them the message hash first |
470500

471501
### Deployment options
472502

src/Config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export type StrictStreamrClientOptions = {
6565
* otherwise the client does the transport as self-service and pays the mainnet gas costs
6666
*/
6767
minimumWithdrawTokenWei: BigNumber|number|string
68-
freeWithdraw: boolean
68+
payForTransport: boolean
6969
factoryMainnetAddress: EthereumAddress
7070
factorySidechainAddress: EthereumAddress
7171
templateMainnetAddress: EthereumAddress
@@ -137,7 +137,7 @@ export const STREAM_CLIENT_DEFAULTS: StrictStreamrClientOptions = {
137137
tokenSidechainAddress: '0xE4a2620edE1058D61BEe5F45F6414314fdf10548',
138138
dataUnion: {
139139
minimumWithdrawTokenWei: '1000000',
140-
freeWithdraw: false,
140+
payForTransport: true,
141141
factoryMainnetAddress: '0x7d55f9981d4E10A193314E001b96f72FCc901e40',
142142
factorySidechainAddress: '0x1b55587Beea0b5Bc96Bb2ADa56bD692870522e9f',
143143
templateMainnetAddress: '0x5FE790E3751dd775Cb92e9086Acd34a2adeB8C7b',

src/dataunion/Contracts.ts

Lines changed: 4 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { BigNumber } from '@ethersproject/bignumber'
1212
import StreamrEthereum from '../Ethereum'
1313
import { StreamrClient } from '../StreamrClient'
1414

15-
const log = debug('StreamrClient::DataUnion')
15+
const log = debug('StreamrClient::Contracts')
1616

1717
function validateAddress(name: string, address: EthereumAddress) {
1818
if (!isAddress(address)) {
@@ -137,7 +137,7 @@ export class Contracts {
137137
}
138138

139139
// move signatures from sidechain to mainnet
140-
async transportSignaturesForMessage(messageHash: string) {
140+
async transportSignaturesForMessage(messageHash: string): Promise<ContractReceipt | null> {
141141
const sidechainAmb = await this.getSidechainAmb()
142142
const message = await sidechainAmb.message(messageHash)
143143
const messageId = '0x' + message.substr(2, 64)
@@ -149,7 +149,7 @@ export class Contracts {
149149

150150
const [vArray, rArray, sArray]: Todo = [[], [], []]
151151
signatures.forEach((signature: string, i) => {
152-
log(` Signature ${i}: ${signature} (len=${signature.length}=${signature.length / 2 - 1} bytes)`)
152+
log(` Signature ${i}: ${signature} (len=${signature.length} = ${signature.length / 2 - 1} bytes)`)
153153
rArray.push(signature.substr(2, 64))
154154
sArray.push(signature.substr(66, 64))
155155
vArray.push(signature.substr(130, 2))
@@ -171,7 +171,7 @@ export class Contracts {
171171
const alreadyProcessed = await mainnetAmb.relayedMessages(messageId)
172172
if (alreadyProcessed) {
173173
log(`WARNING: Tried to transport signatures but they have already been transported (Message ${messageId} has already been processed)`)
174-
log('This could happen if freeWithdraw=false (attempt self-service), but bridge actually paid before your client')
174+
log('This could happen if bridge paid for transport before your client.')
175175
return null
176176
}
177177

@@ -218,45 +218,6 @@ export class Contracts {
218218
return trAMB
219219
}
220220

221-
async transportSignaturesForTransaction(tr: ContractReceipt, options: { pollingIntervalMs?: number, retryTimeoutMs?: number } = {}) {
222-
const {
223-
pollingIntervalMs = 1000,
224-
retryTimeoutMs = 60000,
225-
} = options
226-
log(`Got receipt, filtering UserRequestForSignature from ${tr.events!.length} events...`)
227-
// event UserRequestForSignature(bytes32 indexed messageId, bytes encodedData);
228-
const sigEventArgsArray = tr.events!.filter((e: Todo) => e.event === 'UserRequestForSignature').map((e: Todo) => e.args)
229-
if (sigEventArgsArray.length < 1) {
230-
throw new Error("No UserRequestForSignature events emitted from withdraw transaction, can't transport withdraw to mainnet")
231-
}
232-
233-
/* eslint-disable no-await-in-loop */
234-
// eslint-disable-next-line no-restricted-syntax
235-
for (const eventArgs of sigEventArgsArray) {
236-
const messageId = eventArgs[0]
237-
const messageHash = keccak256(eventArgs[1])
238-
239-
log(`Waiting until sidechain AMB has collected required signatures for hash=${messageHash}...`)
240-
await until(async () => this.requiredSignaturesHaveBeenCollected(messageHash), pollingIntervalMs, retryTimeoutMs)
241-
242-
log(`Checking mainnet AMB hasn't already processed messageId=${messageId}`)
243-
const mainnetAmb = await this.getMainnetAmb()
244-
const alreadySent = await mainnetAmb.messageCallStatus(messageId)
245-
const failAddress = await mainnetAmb.failedMessageSender(messageId)
246-
247-
// zero address means no failed messages
248-
if (alreadySent || failAddress !== '0x0000000000000000000000000000000000000000') {
249-
log(`WARNING: Mainnet bridge has already processed withdraw messageId=${messageId}`)
250-
log('This could happen if freeWithdraw=false (attempt self-service), but bridge actually paid before your client')
251-
continue
252-
}
253-
254-
log(`Transporting signatures for hash=${messageHash}`)
255-
await this.transportSignaturesForMessage(messageHash)
256-
}
257-
/* eslint-enable no-await-in-loop */
258-
}
259-
260221
async deployDataUnion({
261222
ownerAddress,
262223
agentAddressList,

0 commit comments

Comments
 (0)