diff --git a/examples/bundler-parcel/package.json b/examples/bundler-parcel/package.json
index fbdf236f..5a9d2656 100644
--- a/examples/bundler-parcel/package.json
+++ b/examples/bundler-parcel/package.json
@@ -1,7 +1,7 @@
{
"name": "bundler-parcel",
"private": true,
- "version": "0.7.0",
+ "version": "0.8.0-alpha.6",
"main": "index.js",
"license": "MIT",
"scripts": {
@@ -9,7 +9,7 @@
"build": "parcel build src/index.html"
},
"dependencies": {
- "@aragon/connect-react": "^0.7.0",
+ "@aragon/connect-react": "^0.8.0-alpha.6",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"string.ify": "^1.0.64"
diff --git a/examples/bundler-webpack-ts-loader/package.json b/examples/bundler-webpack-ts-loader/package.json
index 0c444be0..09882e30 100644
--- a/examples/bundler-webpack-ts-loader/package.json
+++ b/examples/bundler-webpack-ts-loader/package.json
@@ -1,7 +1,7 @@
{
"name": "bundler-webpack-ts-loader",
"main": "dist/index.js",
- "version": "0.7.0",
+ "version": "0.8.0-alpha.6",
"private": true,
"sideEffects": false,
"scripts": {
@@ -10,7 +10,7 @@
"clean": "rm -rf ./dist ./tsconfig.tsbuildinfo"
},
"dependencies": {
- "@aragon/connect": "^0.7.0",
+ "@aragon/connect": "^0.8.0-alpha.6",
"react": "^16.13.1",
"react-dom": "^16.13.1"
},
diff --git a/examples/connect-react-intro/package.json b/examples/connect-react-intro/package.json
index f735c674..77dc6264 100644
--- a/examples/connect-react-intro/package.json
+++ b/examples/connect-react-intro/package.json
@@ -1,7 +1,7 @@
{
"name": "connect-react-intro",
"private": true,
- "version": "0.7.0",
+ "version": "0.8.0-alpha.6",
"scripts": {
"start": "snowpack dev",
"build": "snowpack build"
diff --git a/examples/forwarding-path/.gitignore b/examples/forwarding-path/.gitignore
new file mode 100644
index 00000000..68979180
--- /dev/null
+++ b/examples/forwarding-path/.gitignore
@@ -0,0 +1,2 @@
+dist/
+.cache/
diff --git a/examples/forwarding-path/index.html b/examples/forwarding-path/index.html
new file mode 100644
index 00000000..004b9986
--- /dev/null
+++ b/examples/forwarding-path/index.html
@@ -0,0 +1,136 @@
+
+
+
+
+
+ Forwarding path demo
+
+
+
+
+
+
+
+
diff --git a/examples/forwarding-path/index.js b/examples/forwarding-path/index.js
new file mode 100644
index 00000000..e3dce398
--- /dev/null
+++ b/examples/forwarding-path/index.js
@@ -0,0 +1,417 @@
+import ReactDOM from 'react-dom'
+import React, { useEffect, useRef, useState } from 'react'
+import {
+ Connect,
+ createAppHook,
+ useApp,
+ useOrganization,
+} from '@aragon/connect-react'
+import connectVoting from '@aragon/connect-voting'
+import connectTokens from '@aragon/connect-tokens'
+import { useWallet, UseWalletProvider } from 'use-wallet'
+
+const useVoting = createAppHook(connectVoting)
+
+function voteId(vote) {
+ const id = parseInt(vote.id.match(/voteId:(.+)$/)?.[1], 16)
+ return String(id + 1)
+}
+
+function Vote({ vote }) {
+ return (
+
+ Vote {voteId(vote)}:{' '}
+ {vote.metadata ? `“${vote.metadata}”` : (action)}
+
+ )
+}
+
+function App() {
+ const wallet = useWallet()
+ const [org, orgStatus] = useOrganization()
+ const [voting, votingStatus] = useApp('voting')
+ const [tokens, tokensStatus] = useApp('token-manager')
+ const [votes = [], votesStatus] = useVoting(voting, (app) => app.onVotes())
+
+ const [showVotes, setShowVotes] = useState(false)
+ const [
+ {
+ intent,
+ status: intentStatus,
+ description: intentDescription,
+ receipts: intentReceipts,
+ },
+ setIntent,
+ ] = useState({
+ status: 'none',
+ description: null,
+ intent: null,
+ receipts: null,
+ })
+
+ const cancelIntent = useRef(null)
+
+ const initIntent = (cb) => {
+ cancelIntent.current?.()
+ cancelIntent.current = cb
+ setIntent({
+ status: 'loading',
+ description: null,
+ intent: null,
+ receipts: null,
+ })
+ }
+
+ const resetIntent = () => {
+ cancelIntent.current?.()
+ setIntent({
+ status: 'none',
+ description: null,
+ intent: null,
+ receipts: null,
+ })
+ }
+
+ const signIntent = async () => {
+ setIntent((intent) => ({ ...intent, status: 'signing' }))
+
+ let receipts
+
+ try {
+ receipts = await intent.sign((tx) => {
+ return wallet.ethereum.request({
+ method: 'eth_sendTransaction',
+ params: [tx],
+ })
+ })
+ } catch (err) {
+ alert('Error! Check the console for details.')
+ console.error('Signing error:', err)
+ }
+
+ setIntent((intent) => ({
+ ...intent,
+ status: 'signed',
+ receipts,
+ }))
+ }
+
+ const createVote = async () => {
+ let cancelled = false
+ initIntent(() => (cancelled = true))
+
+ const intent = await voting.intent(
+ 'newVote(bytes,string)',
+ ['0x00000001', 'A vote'],
+ { actAs: wallet.account }
+ )
+
+ const description = await intent.describe()
+
+ if (!cancelled) {
+ setIntent({ status: 'loaded', intent, description, receipts: null })
+ }
+ }
+
+ const assignOneToken = async () => {
+ let cancelled = false
+ initIntent(() => (cancelled = true))
+
+ const intent = await tokens.intent(
+ 'mint(address,uint256)',
+ [wallet.account, '1'],
+ { actAs: wallet.account }
+ )
+
+ const description = await intent.describe()
+
+ if (!cancelled) {
+ setIntent({ status: 'loaded', description, intent, receipts: null })
+ }
+ }
+
+ if (wallet.status !== 'connected') {
+ return
+ }
+
+ return (
+
+
+
Forwarding path demo
+
+
Summary
+
+
+
+ Account |
+ {wallet.account} |
+
+
+ Organization |
+ {org.address} |
+
+
+ Tokens |
+ {tokens?.address || 'loading…'} |
+
+
+ Voting |
+ {voting?.address || 'loading…'} |
+
+
+
+
+
+
+
Actions
+
+ Tokens:
+ {tokens ? (
+
+ ) : (
+ 'loading…'
+ )}
+
+
+
+
+
+ Voting:
+
+ {voting ? (
+ <>
+
+
+ >
+ ) : (
+ 'loading…'
+ )}
+
+
+
+
+
+ Intent
+ {intentStatus !== 'none' && (
+
+ )}
+ {['loaded', 'signing'].includes(intentStatus) && (
+
+ )}
+
+
+ {intentStatus === 'none' && (
+ Please use one of the actions to create an intent.
+ )}
+
+ {intentStatus === 'loading' && Creating intent…
}
+
+ {intentStatus !== 'none' && (
+ <>
+ {intentStatus === 'signed' && (
+ <>
+ Transactions receipts
+
+
+ {intentReceipts.map((receipt) => (
+
+ {receipt} |
+
+ ))}
+
+
+ >
+ )}
+
+ {intent?.destination && (
+ <>
+ Destination
+
+
+
+ Name: |
+ {intent.destination.name} |
+
+
+ Address: |
+ {intent.destination.address} |
+
+
+
+ >
+ )}
+
+ {intentDescription && (
+ <>
+ Steps
+
+
+
+ From |
+ To |
+ Description |
+
+
+
+ {intentDescription.describedSteps.map(
+ (
+ { annotatedDescription, data, description, from, to },
+ index
+ ) => (
+
+
+ {shortenAddress(from)}
+ |
+
+ {shortenAddress(to)}
+ |
+
+ {annotatedDescription.map(
+ ({ type, value }, index) => {
+ const prefix = index > 0 ? ' ' : ''
+ if (type === 'address') {
+ return (
+
+ {prefix + shortenAddress(value)}
+
+ )
+ }
+ return (
+
+ {prefix + String(value)}
+
+ )
+ }
+ )}
+ .
+ |
+
+ )
+ )}
+
+
+ >
+ )}
+
+ {intent?.transactions && (
+ <>
+ Transactions
+
+
+
+ From |
+ To |
+ Data |
+
+
+
+ {intent.transactions.map(({ data, from, to }, index) => (
+
+
+ {shortenAddress(from)}
+ |
+
+ {shortenAddress(to)}
+ |
+ {data} |
+
+ ))}
+
+
+ >
+ )}
+ >
+ )}
+
+
+ {showVotes && (
+
+ Votes
+
+ {(!voting || votes.length === 0) && loading…
}
+
+ {votes.length > 0 && (
+
+ {[...votes]
+ .sort((a, b) => {
+ console.log(a)
+ return 0
+ })
+ .map((vote) => (
+ -
+
+
+ ))}
+
+ )}
+
+ )}
+
+ )
+}
+
+function ConnectOverlay() {
+ const wallet = useWallet()
+ const connect = () => wallet.connect('injected')
+ const disconnect = () => wallet.reset()
+
+ return (
+
+ {wallet.status === 'disconnected' ? (
+
+ ) : (
+
+ )}
+
+ )
+}
+
+export function shortenAddress(address, charsLength = 4) {
+ const prefixLength = 2 // "0x"
+ if (!address) {
+ return ''
+ }
+ if (address.length < charsLength * 2 + prefixLength) {
+ return address
+ }
+ return (
+ address.slice(0, charsLength + prefixLength) +
+ '…' +
+ address.slice(-charsLength)
+ )
+}
+
+ReactDOM.render(
+
+
+
+
+ ,
+ document.querySelector('#app')
+)
diff --git a/examples/forwarding-path/package.json b/examples/forwarding-path/package.json
new file mode 100644
index 00000000..bad386a0
--- /dev/null
+++ b/examples/forwarding-path/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "forwarding-path",
+ "private": true,
+ "version": "0.8.0-alpha.1",
+ "main": "index.js",
+ "license": "MIT",
+ "scripts": {
+ "start": "parcel index.html",
+ "build": "parcel build index.html"
+ },
+ "dependencies": {
+ "@aragon/connect-react": "^0.8.0-alpha.1",
+ "@aragon/connect-tokens": "^0.7.0",
+ "@aragon/connect-voting": "^0.7.0",
+ "react": "^16.12.0",
+ "react-dom": "^16.13.1",
+ "use-wallet": "^0.8.0"
+ },
+ "devDependencies": {
+ "parcel": "^1.12.4"
+ },
+ "browserslist": [
+ ">2%"
+ ]
+}
diff --git a/examples/list-balances-cli/package.json b/examples/list-balances-cli/package.json
index afdce8fc..3a4200d7 100644
--- a/examples/list-balances-cli/package.json
+++ b/examples/list-balances-cli/package.json
@@ -1,7 +1,7 @@
{
"name": "list-balances-cli",
"private": true,
- "version": "0.7.0",
+ "version": "0.8.0-alpha.6",
"main": "index.js",
"license": "MIT",
"scripts": {
@@ -13,8 +13,8 @@
"typescript": "^3.9.6"
},
"dependencies": {
- "@aragon/connect": "^0.7.0",
- "@aragon/connect-voting": "^0.7.0",
+ "@aragon/connect": "^0.8.0-alpha.6",
+ "@aragon/connect-voting": "^0.8.0-alpha.6",
"token-amount": "^0.1.0"
}
}
diff --git a/examples/list-votes-cli/package.json b/examples/list-votes-cli/package.json
index 4d8dff5e..45d6869a 100644
--- a/examples/list-votes-cli/package.json
+++ b/examples/list-votes-cli/package.json
@@ -1,7 +1,7 @@
{
"name": "list-votes-cli",
"private": true,
- "version": "0.7.0",
+ "version": "0.8.0-alpha.6",
"main": "index.js",
"license": "MIT",
"scripts": {
@@ -13,7 +13,7 @@
"typescript": "^3.9.6"
},
"dependencies": {
- "@aragon/connect": "^0.7.0",
- "@aragon/connect-voting": "^0.7.0"
+ "@aragon/connect": "^0.8.0-alpha.6",
+ "@aragon/connect-voting": "^0.8.0-alpha.6"
}
}
diff --git a/examples/list-votes-react/package.json b/examples/list-votes-react/package.json
index 4d4b3706..deecec2b 100644
--- a/examples/list-votes-react/package.json
+++ b/examples/list-votes-react/package.json
@@ -1,14 +1,14 @@
{
"name": "list-votes-react",
"private": true,
- "version": "0.7.0",
+ "version": "0.8.0-alpha.6",
"scripts": {
"start": "snowpack dev",
"build": "snowpack build"
},
"dependencies": {
- "@aragon/connect-react": "^0.7.0",
- "@aragon/connect-voting": "^0.7.0",
+ "@aragon/connect-react": "^0.8.0-alpha.6",
+ "@aragon/connect-voting": "^0.8.0-alpha.6",
"@emotion/core": "^10.0.35",
"ethers": "^5.0.16",
"react": "^16.13.1",
diff --git a/examples/minimal-setup-web/package.json b/examples/minimal-setup-web/package.json
index 874e89b8..b0c8f9af 100644
--- a/examples/minimal-setup-web/package.json
+++ b/examples/minimal-setup-web/package.json
@@ -1,7 +1,7 @@
{
"name": "minimal-setup-web",
"private": true,
- "version": "0.7.0",
+ "version": "0.8.0-alpha.6",
"main": "index.js",
"license": "MIT",
"scripts": {
@@ -9,7 +9,7 @@
"build": "parcel build index.html"
},
"dependencies": {
- "@aragon/connect-react": "^0.7.0",
+ "@aragon/connect-react": "^0.8.0-alpha.6",
"react": "^16.13.1",
"react-dom": "^16.13.1"
},
diff --git a/examples/nodejs/package.json b/examples/nodejs/package.json
index 0e3f28b5..7f0e7530 100644
--- a/examples/nodejs/package.json
+++ b/examples/nodejs/package.json
@@ -1,7 +1,7 @@
{
"name": "nodejs",
"main": "dist/index.js",
- "version": "0.7.0",
+ "version": "0.8.0-alpha.6",
"private": true,
"scripts": {
"build": "yarn clean && yarn compile",
@@ -22,10 +22,10 @@
"start-create-karma-template": "yarn build; node dist/src/create-karma-template.js"
},
"dependencies": {
- "@aragon/connect": "^0.7.0",
- "@aragon/connect-thegraph": "^0.7.0",
- "@aragon/connect-tokens": "^0.7.0",
- "@aragon/connect-voting": "^0.7.0",
+ "@aragon/connect": "^0.8.0-alpha.6",
+ "@aragon/connect-thegraph": "^0.8.0-alpha.6",
+ "@aragon/connect-tokens": "^0.8.0-alpha.6",
+ "@aragon/connect-voting": "^0.8.0-alpha.6",
"ethers": "^5.0.0",
"graphql-tag": "^2.10.3"
},
diff --git a/examples/org-viewer-react/package.json b/examples/org-viewer-react/package.json
index 9d3f07b8..d94d17df 100644
--- a/examples/org-viewer-react/package.json
+++ b/examples/org-viewer-react/package.json
@@ -1,6 +1,6 @@
{
"name": "org-viewer-react",
- "version": "0.7.0",
+ "version": "0.8.0-alpha.6",
"private": true,
"scripts": {
"start": "yarn clean && webpack-dev-server",
@@ -9,7 +9,7 @@
"clean": "rm -rf ./dist ./tsconfig.tsbuildinfo"
},
"dependencies": {
- "@aragon/connect-react": "^0.7.0",
+ "@aragon/connect-react": "^0.8.0-alpha.6",
"@emotion/core": "^10.0.28",
"react": "^16.13.1",
"react-dom": "^16.13.1"
diff --git a/examples/org-viewer-web/package.json b/examples/org-viewer-web/package.json
index 9bc6e8a7..2094ba53 100644
--- a/examples/org-viewer-web/package.json
+++ b/examples/org-viewer-web/package.json
@@ -1,7 +1,7 @@
{
"name": "org-viewer-web",
"main": "dist/index.js",
- "version": "0.7.0",
+ "version": "0.8.0-alpha.6",
"private": true,
"sideEffects": false,
"scripts": {
@@ -12,7 +12,7 @@
"compile": "tsc"
},
"dependencies": {
- "@aragon/connect": "^0.7.0",
+ "@aragon/connect": "^0.8.0-alpha.6",
"@emotion/core": "^10.0.28",
"react": "^16.13.1",
"react-dom": "^16.13.1"
diff --git a/package.json b/package.json
index 58280714..214c3c2d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@aragon/connect",
- "version": "0.7.0",
+ "version": "0.8.0-alpha.6",
"description": "Access and interact with Aragon Organizations and their apps.",
"keywords": [
"ethereum",
diff --git a/packages/connect-agreement/jest.config.js b/packages/connect-agreement/jest.config.js
index f169f0dd..b41f5c9e 100644
--- a/packages/connect-agreement/jest.config.js
+++ b/packages/connect-agreement/jest.config.js
@@ -11,4 +11,5 @@ module.exports = {
name: packageName,
displayName: 'AGREEMENT',
rootDir: '../..',
+ testTimeout: 10000
}
diff --git a/packages/connect-agreement/package.json b/packages/connect-agreement/package.json
index 3ab6d92d..801fe48a 100644
--- a/packages/connect-agreement/package.json
+++ b/packages/connect-agreement/package.json
@@ -1,6 +1,6 @@
{
"name": "@aragon/connect-agreement",
- "version": "0.7.0",
+ "version": "0.8.0-alpha.6",
"private": true,
"license": "LGPL-3.0-or-later",
"description": "Access and interact with Aragon Organizations and their apps.",
@@ -36,8 +36,8 @@
"lint": "eslint --ext .ts ./src"
},
"dependencies": {
- "@aragon/connect-core": "^0.7.0",
- "@aragon/connect-thegraph": "^0.7.0",
+ "@aragon/connect-core": "^0.8.0-alpha.6",
+ "@aragon/connect-thegraph": "^0.8.0-alpha.6",
"graphql-tag": "^2.10.3"
},
"devDependencies": {
diff --git a/packages/connect-agreement/src/__test__/connector/agreement.test.ts b/packages/connect-agreement/src/__test__/connector/agreement.test.ts
index 77bf3def..9f12aea1 100644
--- a/packages/connect-agreement/src/__test__/connector/agreement.test.ts
+++ b/packages/connect-agreement/src/__test__/connector/agreement.test.ts
@@ -2,8 +2,8 @@ import { AgreementData } from '../../types'
import { AgreementConnectorTheGraph } from '../../../src'
const AGREEMENT_SUBGRAPH_URL =
- 'https://api.thegraph.com/subgraphs/name/aragon/aragon-agreement-rinkeby-staging'
-const AGREEMENT_APP_ADDRESS = '0x9c92dbd8a8e5903e2741202321073091109f26be'
+ 'https://api.thegraph.com/subgraphs/name/facuspagnuolo/aragon-agreement-rinkeby-staging'
+const AGREEMENT_APP_ADDRESS = '0xe4575381f0c96f58bd93be6978cc0d9638d874a2'
describe('Agreement', () => {
let connector: AgreementConnectorTheGraph
@@ -27,9 +27,9 @@ describe('Agreement', () => {
test('returns the agreement data', () => {
expect(agreement.id).toBe(AGREEMENT_APP_ADDRESS)
- expect(agreement.dao).toBe('0x51a41e43af0774565f0be5cebc50c693cc19e4ee')
+ expect(agreement.dao).toBe('0xe990dd6a81c0fdaad6b5cef44676b383350ad94e')
expect(agreement.stakingFactory).toBe(
- '0x07429001eea415e967c57b8d43484233d57f8b0b'
+ '0x6a30c2de7359db110b6322b41038674ae1d276fb'
)
expect(agreement.currentVersionId).toBe(
`${AGREEMENT_APP_ADDRESS}-version-1`
diff --git a/packages/connect-agreement/src/__test__/connector/signers.test.ts b/packages/connect-agreement/src/__test__/connector/signers.test.ts
index e4bb9813..14c7ba8b 100644
--- a/packages/connect-agreement/src/__test__/connector/signers.test.ts
+++ b/packages/connect-agreement/src/__test__/connector/signers.test.ts
@@ -1,11 +1,12 @@
import { AgreementConnectorTheGraph, Signer, Signature } from '../../../src'
-const AGREEMENT_SUBGRAPH_URL =
- 'https://api.thegraph.com/subgraphs/name/aragon/aragon-agreement-rinkeby-staging'
-const AGREEMENT_APP_ADDRESS = '0x9c92dbd8a8e5903e2741202321073091109f26be'
-const SIGNER_ADDRESS = '0x0090aed150056316e37fe6dfa10dc63e79d173b6'
+const AGREEMENT_APP_ADDRESS = '0xe4575381f0c96f58bd93be6978cc0d9638d874a2'
+const AGREEMENT_SUBGRAPH_URL = 'https://api.thegraph.com/subgraphs/name/facuspagnuolo/aragon-agreement-rinkeby-staging'
describe('Agreement signers', () => {
+ const SIGNER_ADDRESS = '0x0090aed150056316e37fe6dfa10dc63e79d173b6'
+ const SIGNER_ID = `${AGREEMENT_APP_ADDRESS}-signer-${SIGNER_ADDRESS}`
+
let connector: AgreementConnectorTheGraph
beforeAll(() => {
@@ -22,15 +23,11 @@ describe('Agreement signers', () => {
let signer: Signer
beforeAll(async () => {
- signer = await connector.signer(
- `${AGREEMENT_APP_ADDRESS}-signer-${SIGNER_ADDRESS}`
- )
+ signer = (await connector.signer(SIGNER_ID))!
})
test('allows fetching signer information', async () => {
- expect(signer.id).toBe(
- `${AGREEMENT_APP_ADDRESS}-signer-${SIGNER_ADDRESS}`
- )
+ expect(signer.id).toBe(SIGNER_ID)
expect(signer.address).toBe(SIGNER_ADDRESS)
expect(signer.agreementId).toBe(AGREEMENT_APP_ADDRESS)
})
@@ -40,22 +37,16 @@ describe('Agreement signers', () => {
let signatures: Signature[]
beforeAll(async () => {
- signatures = await connector.signatures(
- `${AGREEMENT_APP_ADDRESS}-signer-${SIGNER_ADDRESS}`,
- 1000,
- 0
- )
+ signatures = await connector.signatures(SIGNER_ID, 1000, 0)
})
test('allows fetching signer information', async () => {
expect(signatures.length).toBeGreaterThan(0)
const lastSignature = signatures[signatures.length - 1]
- expect(lastSignature.signerId).toBe(
- `${AGREEMENT_APP_ADDRESS}-signer-${SIGNER_ADDRESS}`
- )
+ expect(lastSignature.signerId).toBe(SIGNER_ID)
expect(lastSignature.versionId).toBe(`${AGREEMENT_APP_ADDRESS}-version-1`)
- expect(lastSignature.createdAt).toBe('1598479718')
+ expect(lastSignature.createdAt).toBe('1599861231')
})
})
})
diff --git a/packages/connect-agreement/src/__test__/connector/versions.test.ts b/packages/connect-agreement/src/__test__/connector/versions.test.ts
index 7adebe0a..aa1b5df8 100644
--- a/packages/connect-agreement/src/__test__/connector/versions.test.ts
+++ b/packages/connect-agreement/src/__test__/connector/versions.test.ts
@@ -1,8 +1,8 @@
import { AgreementConnectorTheGraph, Version } from '../../../src'
const AGREEMENT_SUBGRAPH_URL =
- 'https://api.thegraph.com/subgraphs/name/aragon/aragon-agreement-rinkeby-staging'
-const AGREEMENT_APP_ADDRESS = '0x9c92dbd8a8e5903e2741202321073091109f26be'
+ 'https://api.thegraph.com/subgraphs/name/facuspagnuolo/aragon-agreement-rinkeby-staging'
+const AGREEMENT_APP_ADDRESS = '0xe4575381f0c96f58bd93be6978cc0d9638d874a2'
describe('Agreement versions', () => {
let connector: AgreementConnectorTheGraph
@@ -27,9 +27,9 @@ describe('Agreement versions', () => {
test('returns the current version information', () => {
expect(version.id).toBe(`${AGREEMENT_APP_ADDRESS}-version-1`)
expect(version.versionId).toBe('1')
- expect(version.title).toBe('Aragon Network Cash Agreement')
+ expect(version.title).toBe('Aragon Network DAO Agreement')
expect(version.content).toEqual(
- '0x697066733a516d50766657554e743357725a37756142315a77456d6563335a723141424c39436e63534466517970576b6d6e70'
+ '0x697066733a516d646159544a6b36615632706d56527839456456386b64447844397947466b7464366846736b585372344b4445'
)
expect(version.arbitrator).toBe(
'0x52180af656a1923024d1accf1d827ab85ce48878'
@@ -37,7 +37,7 @@ describe('Agreement versions', () => {
expect(version.appFeesCashier).toBe(
'0x0000000000000000000000000000000000000000'
)
- expect(version.effectiveFrom).toBe('1598475758')
+ expect(version.effectiveFrom).toBe('1599860871')
})
})
@@ -51,9 +51,9 @@ describe('Agreement versions', () => {
test('returns the requested version information', () => {
expect(version.id).toBe(`${AGREEMENT_APP_ADDRESS}-version-1`)
expect(version.versionId).toBe('1')
- expect(version.title).toBe('Aragon Network Cash Agreement')
+ expect(version.title).toBe('Aragon Network DAO Agreement')
expect(version.content).toEqual(
- '0x697066733a516d50766657554e743357725a37756142315a77456d6563335a723141424c39436e63534466517970576b6d6e70'
+ '0x697066733a516d646159544a6b36615632706d56527839456456386b64447844397947466b7464366846736b585372344b4445'
)
expect(version.arbitrator).toBe(
'0x52180af656a1923024d1accf1d827ab85ce48878'
@@ -61,7 +61,7 @@ describe('Agreement versions', () => {
expect(version.appFeesCashier).toBe(
'0x0000000000000000000000000000000000000000'
)
- expect(version.effectiveFrom).toBe('1598475758')
+ expect(version.effectiveFrom).toBe('1599860871')
})
})
@@ -79,9 +79,9 @@ describe('Agreement versions', () => {
test('allows fetching a single version', () => {
const version = versions[0]
- expect(version.title).toBe('Aragon Network Cash Agreement')
+ expect(version.title).toBe('Aragon Network DAO Agreement')
expect(version.content).toEqual(
- '0x697066733a516d50766657554e743357725a37756142315a77456d6563335a723141424c39436e63534466517970576b6d6e70'
+ '0x697066733a516d646159544a6b36615632706d56527839456456386b64447844397947466b7464366846736b585372344b4445'
)
expect(version.arbitrator).toBe(
'0x52180af656a1923024d1accf1d827ab85ce48878'
@@ -89,7 +89,7 @@ describe('Agreement versions', () => {
expect(version.appFeesCashier).toBe(
'0x0000000000000000000000000000000000000000'
)
- expect(version.effectiveFrom).toBe('1598475758')
+ expect(version.effectiveFrom).toBe('1599860871')
})
})
})
diff --git a/packages/connect-agreement/src/__test__/models/agreement.test.ts b/packages/connect-agreement/src/__test__/models/agreement.test.ts
index 2b4ca819..08179cb8 100644
--- a/packages/connect-agreement/src/__test__/models/agreement.test.ts
+++ b/packages/connect-agreement/src/__test__/models/agreement.test.ts
@@ -1,17 +1,23 @@
+import { ethers } from 'ethers'
+import { connect } from '@aragon/connect'
+
+import { bn } from '../../helpers'
import { Agreement, Signer, AgreementConnectorTheGraph } from '../../../src'
-const AGREEMENT_SUBGRAPH_URL =
- 'https://api.thegraph.com/subgraphs/name/aragon/aragon-agreement-rinkeby-staging'
-const AGREEMENT_APP_ADDRESS = '0x9c92dbd8a8e5903e2741202321073091109f26be'
+const RINKEBY_NETWORK = 4
+const ORGANIZATION_NAME = '0x6322eb0294c6aadb7e1b37d41fd605a34df661dc'
+const AGREEMENT_APP_ADDRESS = '0xe4575381f0c96f58bd93be6978cc0d9638d874a2'
+const AGREEMENT_SUBGRAPH_URL = 'https://api.thegraph.com/subgraphs/name/facuspagnuolo/aragon-agreement-rinkeby-staging'
+
describe('Agreement', () => {
let agreement: Agreement
- beforeAll(() => {
- const connector = new AgreementConnectorTheGraph({
- subgraphUrl: AGREEMENT_SUBGRAPH_URL,
- })
- agreement = new Agreement(connector, AGREEMENT_APP_ADDRESS)
+ beforeAll(async () => {
+ const organization = await connect(ORGANIZATION_NAME, 'thegraph', { network: RINKEBY_NETWORK })
+ const connector = new AgreementConnectorTheGraph({ subgraphUrl: AGREEMENT_SUBGRAPH_URL })
+ const app = await organization.connection.orgConnector.appByAddress(organization, AGREEMENT_APP_ADDRESS)
+ agreement = new Agreement(connector, app)
})
afterAll(async () => {
@@ -24,15 +30,11 @@ describe('Agreement', () => {
})
test('has a staking factory', async () => {
- expect(await agreement.stakingFactory()).toBe(
- '0x07429001eea415e967c57b8d43484233d57f8b0b'
- )
+ expect(await agreement.stakingFactory()).toBe('0x6a30c2de7359db110b6322b41038674ae1d276fb')
})
test('belongs to a DAO', async () => {
- expect(await agreement.dao()).toBe(
- '0x51a41e43af0774565f0be5cebc50c693cc19e4ee'
- )
+ expect(await agreement.dao()).toBe('0xe990dd6a81c0fdaad6b5cef44676b383350ad94e')
})
})
@@ -42,17 +44,11 @@ describe('Agreement', () => {
expect(version.id).toBe(`${AGREEMENT_APP_ADDRESS}-version-1`)
expect(version.versionId).toBe('1')
- expect(version.title).toBe('Aragon Network Cash Agreement')
- expect(version.content).toEqual(
- '0x697066733a516d50766657554e743357725a37756142315a77456d6563335a723141424c39436e63534466517970576b6d6e70'
- )
- expect(version.arbitrator).toBe(
- '0x52180af656a1923024d1accf1d827ab85ce48878'
- )
- expect(version.appFeesCashier).toBe(
- '0x0000000000000000000000000000000000000000'
- )
- expect(version.effectiveFrom).toBe('1598475758')
+ expect(version.title).toBe('Aragon Network DAO Agreement')
+ expect(version.content).toEqual('0x697066733a516d646159544a6b36615632706d56527839456456386b64447844397947466b7464366846736b585372344b4445')
+ expect(version.arbitrator).toBe('0x52180af656a1923024d1accf1d827ab85ce48878')
+ expect(version.appFeesCashier).toBe('0x0000000000000000000000000000000000000000')
+ expect(version.effectiveFrom).toBe('1599860871')
})
test('allows querying a particular version', async () => {
@@ -60,17 +56,11 @@ describe('Agreement', () => {
expect(version.id).toBe(`${AGREEMENT_APP_ADDRESS}-version-1`)
expect(version.versionId).toBe('1')
- expect(version.title).toBe('Aragon Network Cash Agreement')
- expect(version.content).toEqual(
- '0x697066733a516d50766657554e743357725a37756142315a77456d6563335a723141424c39436e63534466517970576b6d6e70'
- )
- expect(version.arbitrator).toBe(
- '0x52180af656a1923024d1accf1d827ab85ce48878'
- )
- expect(version.appFeesCashier).toBe(
- '0x0000000000000000000000000000000000000000'
- )
- expect(version.effectiveFrom).toBe('1598475758')
+ expect(version.title).toBe('Aragon Network DAO Agreement')
+ expect(version.content).toEqual('0x697066733a516d646159544a6b36615632706d56527839456456386b64447844397947466b7464366846736b585372344b4445')
+ expect(version.arbitrator).toBe('0x52180af656a1923024d1accf1d827ab85ce48878')
+ expect(version.appFeesCashier).toBe('0x0000000000000000000000000000000000000000')
+ expect(version.effectiveFrom).toBe('1599860871')
})
test('allows fetching a list of versions', async () => {
@@ -78,17 +68,11 @@ describe('Agreement', () => {
expect(versions.length).toBeGreaterThan(0)
const version = versions[0]
- expect(version.title).toBe('Aragon Network Cash Agreement')
- expect(version.content).toEqual(
- '0x697066733a516d50766657554e743357725a37756142315a77456d6563335a723141424c39436e63534466517970576b6d6e70'
- )
- expect(version.arbitrator).toBe(
- '0x52180af656a1923024d1accf1d827ab85ce48878'
- )
- expect(version.appFeesCashier).toBe(
- '0x0000000000000000000000000000000000000000'
- )
- expect(version.effectiveFrom).toBe('1598475758')
+ expect(version.title).toBe('Aragon Network DAO Agreement')
+ expect(version.content).toEqual('0x697066733a516d646159544a6b36615632706d56527839456456386b64447844397947466b7464366846736b585372344b4445')
+ expect(version.arbitrator).toBe('0x52180af656a1923024d1accf1d827ab85ce48878')
+ expect(version.appFeesCashier).toBe('0x0000000000000000000000000000000000000000')
+ expect(version.effectiveFrom).toBe('1599860871')
})
})
@@ -98,24 +82,18 @@ describe('Agreement', () => {
expect(disputables.length).toBeGreaterThan(0)
const disputable = disputables[0]
- expect(disputable.id).toBe(
- `${AGREEMENT_APP_ADDRESS}-disputable-${disputable.address}`
- )
- expect(disputable.address).toBe(
- '0x0e835020497b2cd716369f8fc713fb7bd0a22dbf'
- )
+ expect(disputable.id).toBe(`${AGREEMENT_APP_ADDRESS}-disputable-${disputable.address}`)
+ expect(disputable.address).toBe('0xfae0084f0171fbebf86476b9ac962680c4aa1564')
expect(disputable.activated).toEqual(true)
expect(disputable.agreementId).toBe(AGREEMENT_APP_ADDRESS)
- expect(disputable.collateralRequirementId).toBe(
- `${AGREEMENT_APP_ADDRESS}-disputable-${disputable.address}-collateral-requirement-1`
- )
+ expect(disputable.currentCollateralRequirementId).toBe(`${AGREEMENT_APP_ADDRESS}-disputable-${disputable.address}-collateral-requirement-1`)
const collateralRequirement = await disputable.collateralRequirement()
- expect(collateralRequirement.actionAmount).toEqual('0')
- expect(collateralRequirement.formattedActionAmount).toEqual('0.00')
- expect(collateralRequirement.challengeAmount).toBe('0')
- expect(collateralRequirement.formattedChallengeAmount).toBe('0.00')
- expect(collateralRequirement.challengeDuration).toBe('259200')
+ expect(collateralRequirement.actionAmount).toEqual('5000000000000000000')
+ expect(collateralRequirement.formattedActionAmount).toEqual('5.00')
+ expect(collateralRequirement.challengeAmount).toBe('10000000000000000000')
+ expect(collateralRequirement.formattedChallengeAmount).toBe('10.00')
+ expect(collateralRequirement.challengeDuration).toBe('300')
const erc20 = await collateralRequirement.token()
expect(erc20.decimals).toEqual(18)
@@ -125,36 +103,43 @@ describe('Agreement', () => {
})
describe('signers', () => {
- let signer: Signer
- const SIGNER_ADDRESS = '0x0090aed150056316e37fe6dfa10dc63e79d173b6'
+ describe('when the signer has signed', () => {
+ let signer: Signer
+ const SIGNER_ADDRESS = '0x0090aed150056316e37fe6dfa10dc63e79d173b6'
- beforeAll(async () => {
- signer = await agreement.signer(SIGNER_ADDRESS)
- })
+ beforeAll(async () => {
+ signer = (await agreement.signer(SIGNER_ADDRESS))!
+ })
- test('allows querying a particular signer', async () => {
- expect(signer.id).toBe(
- `${AGREEMENT_APP_ADDRESS}-signer-${SIGNER_ADDRESS}`
- )
- expect(signer.address).toBe(SIGNER_ADDRESS)
- expect(signer.agreementId).toBe(AGREEMENT_APP_ADDRESS)
- })
+ test('allows querying a particular signer', async () => {
+ expect(signer.id).toBe(`${AGREEMENT_APP_ADDRESS}-signer-${SIGNER_ADDRESS}`)
+ expect(signer.address).toBe(SIGNER_ADDRESS)
+ expect(signer.agreementId).toBe(AGREEMENT_APP_ADDRESS)
+ })
+
+ test('allows telling if a signer signed a version', async () => {
+ expect(await signer.hasSigned('1')).toBe(true)
+ expect(await signer.hasSigned('1000')).toBe(false)
+ })
- test('allows telling if a signer signed a version', async () => {
- expect(await signer.hasSigned('1')).toBe(true)
- expect(await signer.hasSigned('1000')).toBe(false)
+ test('allows fetching the signatures of the signer', async () => {
+ const signatures = await signer.signatures()
+ expect(signatures.length).toBeGreaterThan(0)
+
+ const lastSignature = signatures[signatures.length - 1]
+ expect(lastSignature.signerId).toBe(`${AGREEMENT_APP_ADDRESS}-signer-${SIGNER_ADDRESS}`)
+ expect(lastSignature.versionId).toBe(`${AGREEMENT_APP_ADDRESS}-version-1`)
+ expect(lastSignature.createdAt).toBe('1599861231')
+ })
})
- test('allows fetching the signatures of the signer', async () => {
- const signatures = await signer.signatures()
- expect(signatures.length).toBeGreaterThan(0)
+ describe('when the signer has not signed', () => {
+ const SIGNER_ADDRESS = '0x0000000000000000000000000000000000000000'
- const lastSignature = signatures[signatures.length - 1]
- expect(lastSignature.signerId).toBe(
- `${AGREEMENT_APP_ADDRESS}-signer-${SIGNER_ADDRESS}`
- )
- expect(lastSignature.versionId).toBe(`${AGREEMENT_APP_ADDRESS}-version-1`)
- expect(lastSignature.createdAt).toBe('1598479718')
+ test('returns null', async () => {
+ const signer = await agreement.signer(SIGNER_ADDRESS)
+ expect(signer).toBeNull()
+ })
})
})
@@ -163,23 +148,23 @@ describe('Agreement', () => {
const USER = '0x0090aed150056316e37fe6dfa10dc63e79d173b6'
test('allows fetching the staking information for a user and a token', async () => {
- const staking = await agreement.staking(TOKEN, USER)
+ const staking = (await agreement.staking(TOKEN, USER))!
- expect(staking.total).toBe('3000000000000000000')
- expect(staking.formattedTotalAmount).toBe('3.00')
+ expect(staking.total).toBe('20000000000000000015')
+ expect(staking.formattedTotalAmount).toBe('20.00')
- expect(staking.locked).toBe('3000000000000000000')
- expect(staking.formattedLockedAmount).toBe('3.00')
+ expect(staking.locked).toBe('15000000000000000001')
+ expect(staking.formattedLockedAmount).toBe('15.00')
- expect(staking.available).toBe('0')
- expect(staking.formattedAvailableAmount).toBe('0.00')
+ expect(staking.available).toBe('5000000000000000014')
+ expect(staking.formattedAvailableAmount).toBe('5.00')
expect(staking.challenged).toBe('0')
expect(staking.formattedChallengedAmount).toBe('0.00')
})
- it('allows accessing the token data', async () => {
- const staking = await agreement.staking(TOKEN, USER)
+ test('allows accessing the token data', async () => {
+ const staking = (await agreement.staking(TOKEN, USER))!
const token = await staking.token()
expect(token.id).toBe(TOKEN)
@@ -195,46 +180,140 @@ describe('Agreement', () => {
test('allows fetching the list of staking movements for a user', async () => {
const movements = await agreement.stakingMovements(TOKEN, USER)
- expect(movements.length).toBeGreaterThan(5)
+ expect(movements.length).toBeGreaterThan(1)
- expect(movements[0].formattedAmount).toBe('1.00')
- expect(movements[0].actionState).toBe('NA')
- expect(movements[0].collateralState).toBe('Available')
+ expect(movements[0].formattedAmount).toBe('5.00')
+ expect(movements[0].actionState).toBe('Scheduled')
+ expect(movements[0].collateralState).toBe('Locked')
- expect(movements[1].formattedAmount).toBe('1.00')
+ expect(movements[1].formattedAmount).toBe('5.00')
expect(movements[1].actionState).toBe('Scheduled')
expect(movements[1].collateralState).toBe('Locked')
})
- describe('when there is an action associated to it', () => {
- const MOVEMENT_ID = 1
+ test('has an agreement action', async () => {
+ const movements = await agreement.stakingMovements(TOKEN, USER)
+ const movement = movements[1]
+
+ expect(movement.agreementId).toBe(AGREEMENT_APP_ADDRESS)
+ expect(movement.actionId).toBe(`${AGREEMENT_APP_ADDRESS}-action-2`)
+ expect(movement.disputableAddress).toBe('0xfae0084f0171fbebf86476b9ac962680c4aa1564')
+ expect(movement.disputableActionId).toBe('1')
- it('has an agreement action', async () => {
- const movements = await agreement.stakingMovements(TOKEN, USER)
- const movement = movements[MOVEMENT_ID]
+ const action = (await movement.action())!
+ expect(action.context).toBe('0x736f6d652066756e6473')
+ })
+ })
- expect(movement.agreementId).toBe(AGREEMENT_APP_ADDRESS)
- expect(movement.actionId).toBe(`${AGREEMENT_APP_ADDRESS}-action-15`)
+ describe('sign', () => {
+ const VERSION_NUMBER = '1'
+ const SIGNER_ADDRESS = '0x0090aed150056316e37fe6dfa10dc63e79d173b6'
- const action = (await movement.action())!
- expect(action.script).toBe('0x00000001')
- expect(action.context).toBe('0x436f6e7465787420666f7220616374696f6e2031')
- })
+ test('returns a sign intent', async () => {
+ const abi = new ethers.utils.Interface(['function sign(uint256)'])
+ const intent = await agreement.sign(SIGNER_ADDRESS, VERSION_NUMBER)
+
+ expect(intent.transactions.length).toBe(1)
+ expect(intent.destination.address).toBe(AGREEMENT_APP_ADDRESS)
+
+ const transaction = intent.transactions[0]
+ expect(transaction.to).toBe(AGREEMENT_APP_ADDRESS)
+ expect(transaction.from).toBe(SIGNER_ADDRESS)
+ expect(transaction.data).toBe(abi.encodeFunctionData('sign', [VERSION_NUMBER]))
})
+ })
- describe('when there is no action associated to it', () => {
- const MOVEMENT_ID = 0
+ describe('settle', () => {
+ const ACTION_NUMBER = '1'
+ const SIGNER_ADDRESS = '0x0090aed150056316e37fe6dfa10dc63e79d173b6'
- it('has no agreement and no action', async () => {
- const movements = await agreement.stakingMovements(TOKEN, USER)
- const movement = await movements[MOVEMENT_ID]
+ test('returns a settle intent', async () => {
+ const abi = new ethers.utils.Interface(['function settleAction(uint256)'])
+ const intent = await agreement.settle(ACTION_NUMBER, SIGNER_ADDRESS)
- expect(movement.actionId).toBe(null)
- expect(movement.agreementId).toBe(null)
+ expect(intent.transactions.length).toBe(1)
+ expect(intent.destination.address).toBe(AGREEMENT_APP_ADDRESS)
- const action = await movement.action()
- expect(action).toBe(null)
- })
+ const transaction = intent.transactions[0]
+ expect(transaction.to).toBe(AGREEMENT_APP_ADDRESS)
+ expect(transaction.from).toBe(SIGNER_ADDRESS)
+ expect(transaction.data).toBe(abi.encodeFunctionData('settleAction', [ACTION_NUMBER]))
+ })
+ })
+
+ describe('close', () => {
+ const ACTION_NUMBER = '1'
+ const SIGNER_ADDRESS = '0x0090aed150056316e37fe6dfa10dc63e79d173b6'
+
+ test('returns a close intent', async () => {
+ const abi = new ethers.utils.Interface(['function closeAction(uint256)'])
+ const intent = await agreement.close(ACTION_NUMBER, SIGNER_ADDRESS)
+
+ expect(intent.transactions.length).toBe(1)
+ expect(intent.destination.address).toBe(AGREEMENT_APP_ADDRESS)
+
+ const transaction = intent.transactions[0]
+ expect(transaction.to).toBe(AGREEMENT_APP_ADDRESS)
+ expect(transaction.from).toBe(SIGNER_ADDRESS)
+ expect(transaction.data).toBe(abi.encodeFunctionData('closeAction', [ACTION_NUMBER]))
+ })
+ })
+
+ describe('challenge', () => {
+ const ACTION_NUMBER = '1'
+ const SETTLEMENT_OFFER = '20'
+ const CONTEXT = 'challenger evidence'
+ const SIGNER_ADDRESS = '0x0090aed150056316e37fe6dfa10dc63e79d173b6'
+
+ test('returns a challenge intent', async () => {
+ const erc20ABI = new ethers.utils.Interface(['function approve(address,uint256) public returns (bool)'])
+ const agreementABI = new ethers.utils.Interface(['function challengeAction(uint256,uint256,bool,bytes)'])
+ const intent = await agreement.challenge(ACTION_NUMBER, SETTLEMENT_OFFER, true, CONTEXT, SIGNER_ADDRESS)
+
+ expect(intent.transactions.length).toBe(2)
+ expect(intent.destination.address).toBe(AGREEMENT_APP_ADDRESS)
+
+ const action = (await agreement.action(ACTION_NUMBER))!
+ const disputeFees = await agreement.disputeFees(action.versionId)
+ const collateralRequirement = await action.collateralRequirement()
+ const expectedApprovalAmount = bn(collateralRequirement.challengeAmount).add(disputeFees.feeAmount)
+
+ const firstTransaction = intent.transactions[0]
+ expect(firstTransaction.to.toLowerCase()).toBe(disputeFees.feeToken.toLowerCase())
+ expect(firstTransaction.from.toLowerCase()).toBe(SIGNER_ADDRESS)
+ expect(firstTransaction.data).toBe(erc20ABI.encodeFunctionData('approve', [AGREEMENT_APP_ADDRESS, expectedApprovalAmount]))
+
+ const secondTransaction = intent.transactions[1]
+ expect(secondTransaction.to.toLowerCase()).toBe(AGREEMENT_APP_ADDRESS)
+ expect(secondTransaction.from).toBe(SIGNER_ADDRESS)
+ expect(secondTransaction.data).toBe(agreementABI.encodeFunctionData('challengeAction', [ACTION_NUMBER, SETTLEMENT_OFFER, true, ethers.utils.toUtf8Bytes(CONTEXT)]))
+ })
+ })
+
+ describe('dispute', () => {
+ const ACTION_NUMBER = '1'
+ const SIGNER_ADDRESS = '0x0090aed150056316e37fe6dfa10dc63e79d173b6'
+
+ test('returns a dispute intent', async () => {
+ const erc20ABI = new ethers.utils.Interface(['function approve(address,uint256) public returns (bool)'])
+ const agreementABI = new ethers.utils.Interface(['function disputeAction(uint256,bool)'])
+ const intent = await agreement.dispute(ACTION_NUMBER, true, SIGNER_ADDRESS)
+
+ expect(intent.transactions.length).toBe(2)
+ expect(intent.destination.address).toBe(AGREEMENT_APP_ADDRESS)
+
+ const action = (await agreement.action(ACTION_NUMBER))!
+ const disputeFees = await agreement.disputeFees(action.versionId)
+
+ const firstTransaction = intent.transactions[0]
+ expect(firstTransaction.to.toLowerCase()).toBe(disputeFees.feeToken.toLowerCase())
+ expect(firstTransaction.from.toLowerCase()).toBe(SIGNER_ADDRESS)
+ expect(firstTransaction.data).toBe(erc20ABI.encodeFunctionData('approve', [AGREEMENT_APP_ADDRESS, disputeFees.feeAmount]))
+
+ const secondTransaction = intent.transactions[1]
+ expect(secondTransaction.to.toLowerCase()).toBe(AGREEMENT_APP_ADDRESS)
+ expect(secondTransaction.from).toBe(SIGNER_ADDRESS)
+ expect(secondTransaction.data).toBe(agreementABI.encodeFunctionData('disputeAction', [ACTION_NUMBER, true]))
})
})
})
diff --git a/packages/connect-agreement/src/connect.ts b/packages/connect-agreement/src/connect.ts
index 19662876..a8a0baa0 100644
--- a/packages/connect-agreement/src/connect.ts
+++ b/packages/connect-agreement/src/connect.ts
@@ -32,6 +32,6 @@ export default createAppConnector(
verbose,
})
- return new Agreement(connectorTheGraph, app.address)
+ return new Agreement(connectorTheGraph, app)
}
)
diff --git a/packages/connect-agreement/src/helpers/numbers.ts b/packages/connect-agreement/src/helpers/numbers.ts
index 8c450fe1..8cc726d6 100644
--- a/packages/connect-agreement/src/helpers/numbers.ts
+++ b/packages/connect-agreement/src/helpers/numbers.ts
@@ -4,7 +4,7 @@ export const PCT_DECIMALS = 16 // 100% = 10^18
export const PCT_BASE = BigNumber.from(`100${'0'.repeat(PCT_DECIMALS)}`)
-export const bn = (x: string): BigNumber => BigNumber.from(x)
+export const bn = (x: string | number): BigNumber => BigNumber.from(x.toString())
export const formatBn = (
number: string | BigNumber,
@@ -29,5 +29,10 @@ export const formatBn = (
const roundedDecimals = Math.round(
parseInt(decimals) / 10 ** (decimalsLength - formattedDecimals)
)
- return `${integer}.${roundedDecimals}`
+
+ const parsedRoundedDecimals = (roundedDecimals === 0)
+ ? '0'.repeat(formattedDecimals)
+ : roundedDecimals.toString()
+
+ return `${integer}.${parsedRoundedDecimals}`
}
diff --git a/packages/connect-agreement/src/models/Action.ts b/packages/connect-agreement/src/models/Action.ts
index bfa3d66a..d44bce7e 100644
--- a/packages/connect-agreement/src/models/Action.ts
+++ b/packages/connect-agreement/src/models/Action.ts
@@ -14,7 +14,6 @@ export default class Action {
readonly disputableActionId: string
readonly collateralRequirementId: string
readonly versionId: string
- readonly script: string
readonly context: string
readonly createdAt: string
@@ -28,7 +27,6 @@ export default class Action {
this.collateralRequirementId = data.collateralRequirementId
this.versionId = data.versionId
this.context = data.context
- this.script = data.script
this.createdAt = data.createdAt
}
diff --git a/packages/connect-agreement/src/models/Agreement.ts b/packages/connect-agreement/src/models/Agreement.ts
index 004a46d5..82abb45f 100644
--- a/packages/connect-agreement/src/models/Agreement.ts
+++ b/packages/connect-agreement/src/models/Agreement.ts
@@ -1,10 +1,9 @@
-import {
- Address,
- SubscriptionCallback,
- SubscriptionResult,
-} from '@aragon/connect-types'
-import { subscription } from '@aragon/connect-core'
+import { Contract, utils } from 'ethers'
+import { SubscriptionCallback, SubscriptionResult } from '@aragon/connect-types'
+import { subscription, App, ForwardingPath, arbitratorAbi } from '@aragon/connect-core'
+import { bn } from '../helpers'
+import Action from './Action'
import Signer from './Signer'
import Version from './Version'
import DisputableApp from './DisputableApp'
@@ -13,12 +12,15 @@ import StakingMovement from './StakingMovement'
import { IAgreementConnector } from '../types'
export default class Agreement {
- #address: Address
+ #app: App
#connector: IAgreementConnector
- constructor(connector: IAgreementConnector, address: Address) {
+ readonly address: string
+
+ constructor(connector: IAgreementConnector, app: App) {
this.#connector = connector
- this.#address = address
+ this.#app = app
+ this.address = app.address
}
async disconnect() {
@@ -26,34 +28,34 @@ export default class Agreement {
}
async id(): Promise {
- const data = await this.#connector.agreement(this.#address)
+ const data = await this.#connector.agreement(this.address)
return data.id
}
async dao(): Promise {
- const data = await this.#connector.agreement(this.#address)
+ const data = await this.#connector.agreement(this.address)
return data.dao
}
async stakingFactory(): Promise {
- const data = await this.#connector.agreement(this.#address)
+ const data = await this.#connector.agreement(this.address)
return data.stakingFactory
}
async currentVersion(): Promise {
- return this.#connector.currentVersion(this.#address)
+ return this.#connector.currentVersion(this.address)
}
onCurrentVersion(
callback?: SubscriptionCallback
): SubscriptionResult {
return subscription(callback, (callback) =>
- this.#connector.onCurrentVersion(this.#address, callback)
+ this.#connector.onCurrentVersion(this.address, callback)
)
}
versionId(versionNumber: string): string {
- return `${this.#address}-version-${versionNumber}`
+ return `${this.address}-version-${versionNumber}`
}
async version(versionNumber: string): Promise {
@@ -69,8 +71,14 @@ export default class Agreement {
)
}
+ async disputeFees(versionId: string): Promise {
+ const version = await this.#connector.version(versionId)
+ const arbitrator = new Contract(version.arbitrator, arbitratorAbi, this.#app.provider)
+ return arbitrator.getDisputeFees()
+ }
+
async versions({ first = 1000, skip = 0 } = {}): Promise {
- return this.#connector.versions(this.#address, first, skip)
+ return this.#connector.versions(this.address, first, skip)
}
onVersions(
@@ -78,14 +86,12 @@ export default class Agreement {
callback?: SubscriptionCallback
): SubscriptionResult {
return subscription(callback, (callback) =>
- this.#connector.onVersions(this.#address, first, skip, callback)
+ this.#connector.onVersions(this.address, first, skip, callback)
)
}
- async disputableApps({ first = 1000, skip = 0 } = {}): Promise<
- DisputableApp[]
- > {
- return this.#connector.disputableApps(this.#address, first, skip)
+ async disputableApps({ first = 1000, skip = 0 } = {}): Promise {
+ return this.#connector.disputableApps(this.address, first, skip)
}
onDisputableApps(
@@ -93,41 +99,41 @@ export default class Agreement {
callback?: SubscriptionCallback
): SubscriptionResult {
return subscription(callback, (callback) =>
- this.#connector.onDisputableApps(this.#address, first, skip, callback)
+ this.#connector.onDisputableApps(this.address, first, skip, callback)
)
}
signerId(signerAddress: string): string {
- return `${this.#address}-signer-${signerAddress.toLowerCase()}`
+ return `${this.address}-signer-${signerAddress.toLowerCase()}`
}
- async signer(signerAddress: string): Promise {
+ async signer(signerAddress: string): Promise {
return this.#connector.signer(this.signerId(signerAddress))
}
onSigner(
signerAddress: string,
- callback?: SubscriptionCallback
- ): SubscriptionResult {
- return subscription(callback, (callback) =>
+ callback?: SubscriptionCallback
+ ): SubscriptionResult {
+ return subscription(callback, (callback) =>
this.#connector.onSigner(this.signerId(signerAddress), callback)
)
}
stakingId(tokenAddress: string, userAddress: string): string {
- return `${tokenAddress.toLowerCase()}-user-${userAddress.toLowerCase()}`
+ return `${tokenAddress.toLowerCase()}-staking-${userAddress.toLowerCase()}`
}
- async staking(tokenAddress: string, userAddress: string): Promise {
+ async staking(tokenAddress: string, userAddress: string): Promise {
return this.#connector.staking(this.stakingId(tokenAddress, userAddress))
}
onStaking(
tokenAddress: string,
userAddress: string,
- callback?: SubscriptionCallback
- ): SubscriptionResult {
- return subscription(callback, (callback) =>
+ callback?: SubscriptionCallback
+ ): SubscriptionResult {
+ return subscription(callback, (callback) =>
this.#connector.onStaking(this.stakingId(tokenAddress, userAddress), callback)
)
}
@@ -137,7 +143,7 @@ export default class Agreement {
userAddress: string,
{ first = 1000, skip = 0 } = {},
): Promise {
- return this.#connector.stakingMovements(this.stakingId(tokenAddress, userAddress), this.#address, first, skip)
+ return this.#connector.stakingMovements(this.stakingId(tokenAddress, userAddress), this.address, first, skip)
}
onStakingMovements(
@@ -147,7 +153,80 @@ export default class Agreement {
callback?: SubscriptionCallback
): SubscriptionResult {
return subscription(callback, (callback) =>
- this.#connector.onStakingMovements(this.stakingId(tokenAddress, userAddress), this.#address, first, skip, callback)
+ this.#connector.onStakingMovements(this.stakingId(tokenAddress, userAddress), this.address, first, skip, callback)
+ )
+ }
+
+ actionId(actionNumber: string): string {
+ return `${this.address.toLowerCase()}-action-${actionNumber}`
+ }
+
+ async action(actionNumber: string): Promise {
+ const action = await this.tryAction(actionNumber)
+ if (!action) {
+ throw Error(`Could not find given action number ${actionNumber}`)
+ }
+ return action as Action
+ }
+
+ async tryAction(actionNumber: string): Promise {
+ return this.#connector.action(this.actionId(actionNumber))
+ }
+
+ onAction(actionNumber: string, callback?: SubscriptionCallback): SubscriptionResult {
+ return subscription(callback, (callback) =>
+ this.#connector.onAction(this.actionId(actionNumber), callback)
)
}
+
+ async sign(signerAddress: string, versionNumber?: string): Promise {
+ if (!versionNumber) {
+ versionNumber = (await this.currentVersion()).versionId
+ }
+ return this.#app.intent('sign', [versionNumber], { actAs: signerAddress })
+ }
+
+ async challenge(actionNumber: string, settlementOffer: string, finishedEvidence: boolean, context: string, signerAddress: string): Promise {
+ const intent = await this.#app.intent('challengeAction', [actionNumber, settlementOffer, finishedEvidence, utils.toUtf8Bytes(context)], { actAs: signerAddress })
+
+ const action = await this.action(actionNumber)
+ const { feeToken, feeAmount } = await this.disputeFees(action.versionId)
+ const { tokenId: collateralToken, challengeAmount } = await action.collateralRequirement()
+ const challengeCollateral = bn(challengeAmount)
+
+ // approve challenge collateral and dispute fees
+ const preTransactions = []
+ if (feeToken.toLowerCase() == collateralToken.toLowerCase()) {
+ const approvalAmount = challengeCollateral.add(feeAmount)
+ const approvePreTransactions = await intent.buildApprovePreTransactions({ address: feeToken, value: approvalAmount })
+ preTransactions.push(...approvePreTransactions)
+ } else {
+ const feesPreTransactions = await intent.buildApprovePreTransactions({ address: feeToken, value: feeAmount })
+ const collateralPreTransactions = await intent.buildApprovePreTransactions({ address: collateralToken, value: challengeCollateral })
+ preTransactions.push(...feesPreTransactions, ...collateralPreTransactions)
+ }
+
+ intent.applyPreTransactions(preTransactions)
+ return intent
+ }
+
+ async dispute(actionNumber: string, finishedEvidence: boolean, signerAddress: string): Promise {
+ const intent = await this.#app.intent('disputeAction', [actionNumber, finishedEvidence], { actAs: signerAddress })
+
+ const action = await this.action(actionNumber)
+ const { feeToken, feeAmount } = await this.disputeFees(action.versionId)
+
+ // approve dispute fees
+ const preTransactions = await intent.buildApprovePreTransactions({ address: feeToken, value: feeAmount })
+ intent.applyPreTransactions(preTransactions)
+ return intent
+ }
+
+ settle(actionNumber: string, signerAddress: string): Promise {
+ return this.#app.intent('settleAction', [actionNumber], { actAs: signerAddress })
+ }
+
+ close(actionNumber: string, signerAddress: string): Promise {
+ return this.#app.intent('closeAction', [actionNumber], { actAs: signerAddress })
+ }
}
diff --git a/packages/connect-agreement/src/models/CollateralRequirement.ts b/packages/connect-agreement/src/models/CollateralRequirement.ts
index 098d0488..b9428ea5 100644
--- a/packages/connect-agreement/src/models/CollateralRequirement.ts
+++ b/packages/connect-agreement/src/models/CollateralRequirement.ts
@@ -10,6 +10,7 @@ export default class CollateralRequirement {
readonly id: string
readonly disputableAppId: string
readonly tokenId: string
+ readonly tokenSymbol: string
readonly tokenDecimals: string
readonly actionAmount: string
readonly challengeAmount: string
@@ -21,6 +22,7 @@ export default class CollateralRequirement {
this.id = data.id
this.disputableAppId = data.disputableAppId
this.tokenId = data.tokenId
+ this.tokenSymbol = data.tokenSymbol
this.tokenDecimals = data.tokenDecimals
this.actionAmount = data.actionAmount
this.challengeAmount = data.challengeAmount
diff --git a/packages/connect-agreement/src/models/DisputableApp.ts b/packages/connect-agreement/src/models/DisputableApp.ts
index 3d8bde35..6c44f634 100644
--- a/packages/connect-agreement/src/models/DisputableApp.ts
+++ b/packages/connect-agreement/src/models/DisputableApp.ts
@@ -9,7 +9,7 @@ export default class DisputableApp {
readonly id: string
readonly address: string
readonly agreementId: string
- readonly collateralRequirementId: string
+ readonly currentCollateralRequirementId: string
readonly activated: boolean
constructor(data: DisputableAppData, connector: IAgreementConnector) {
@@ -18,19 +18,19 @@ export default class DisputableApp {
this.id = data.id
this.address = data.address
this.agreementId = data.agreementId
- this.collateralRequirementId = data.collateralRequirementId
+ this.currentCollateralRequirementId = data.currentCollateralRequirementId
this.activated = data.activated
}
async collateralRequirement(): Promise {
- return this.#connector.collateralRequirement(this.id)
+ return this.#connector.collateralRequirement(this.currentCollateralRequirementId)
}
onCollateralRequirement(
callback?: SubscriptionCallback
): SubscriptionResult {
return subscription(callback, (callback) =>
- this.#connector.onCollateralRequirement(this.id, callback)
+ this.#connector.onCollateralRequirement(this.currentCollateralRequirementId, callback)
)
}
}
diff --git a/packages/connect-agreement/src/models/Staking.ts b/packages/connect-agreement/src/models/Staking.ts
index d560e153..fc953254 100644
--- a/packages/connect-agreement/src/models/Staking.ts
+++ b/packages/connect-agreement/src/models/Staking.ts
@@ -11,6 +11,7 @@ export default class Staking {
readonly id: string
readonly user: string
readonly tokenId: string
+ readonly tokenSymbol: string
readonly tokenDecimals: string
readonly available: string
readonly locked: string
@@ -23,6 +24,7 @@ export default class Staking {
this.id = data.id
this.user = data.user
this.tokenId = data.tokenId
+ this.tokenSymbol = data.tokenSymbol
this.tokenDecimals = data.tokenDecimals
this.available = data.available
this.locked = data.locked
diff --git a/packages/connect-agreement/src/models/StakingMovement.ts b/packages/connect-agreement/src/models/StakingMovement.ts
index bd65926c..24b8894f 100644
--- a/packages/connect-agreement/src/models/StakingMovement.ts
+++ b/packages/connect-agreement/src/models/StakingMovement.ts
@@ -15,9 +15,12 @@ export default class StakingMovement {
readonly stakingId: string
readonly agreementId: string
readonly tokenId: string
+ readonly tokenSymbol: string
readonly tokenDecimals: string
readonly amount: string
readonly actionId: string
+ readonly disputableAddress: string
+ readonly disputableActionId: string
readonly actionState: string
readonly collateralState: string
readonly createdAt: string
@@ -29,9 +32,12 @@ export default class StakingMovement {
this.stakingId = data.stakingId
this.agreementId = data.agreementId
this.tokenId = data.tokenId
+ this.tokenSymbol = data.tokenSymbol
this.tokenDecimals = data.tokenDecimals
this.amount = data.amount
this.actionId = data.actionId
+ this.disputableAddress = data.disputableAddress
+ this.disputableActionId = data.disputableActionId
this.actionState = data.actionState
this.collateralState = data.collateralState
this.createdAt = data.createdAt
@@ -55,18 +61,26 @@ export default class StakingMovement {
)
}
- async staking(): Promise {
+ async staking(): Promise {
return this.#connector.staking(this.stakingId)
}
- onStaking(callback?: SubscriptionCallback): SubscriptionResult {
- return subscription(callback, (callback) =>
+ onStaking(callback?: SubscriptionCallback): SubscriptionResult {
+ return subscription(callback, (callback) =>
this.#connector.onStaking(this.stakingId, callback)
)
}
- async action(): Promise {
- return this.#connector.action(this.actionId || '')
+ async action(): Promise {
+ const action = await this.tryAction()
+ if (!action) {
+ throw Error(`Could not find given action number ${this.actionId}`)
+ }
+ return action as Action
+ }
+
+ async tryAction(): Promise {
+ return this.#connector.action(this.actionId)
}
onAction(callback?: SubscriptionCallback): SubscriptionResult {
diff --git a/packages/connect-agreement/src/thegraph/connector.ts b/packages/connect-agreement/src/thegraph/connector.ts
index 7515784f..7590cb3e 100644
--- a/packages/connect-agreement/src/thegraph/connector.ts
+++ b/packages/connect-agreement/src/thegraph/connector.ts
@@ -198,8 +198,8 @@ export default class AgreementConnectorTheGraph implements IAgreementConnector {
)
}
- async signer(signerId: string): Promise {
- return this.#gql.performQueryWithParser(
+ async signer(signerId: string): Promise {
+ return this.#gql.performQueryWithParser(
queries.GET_SIGNER('query'),
{ signerId },
(result: QueryResult) => parseSigner(result, this)
@@ -208,9 +208,9 @@ export default class AgreementConnectorTheGraph implements IAgreementConnector {
onSigner(
signerId: string,
- callback: SubscriptionCallback
+ callback: SubscriptionCallback
): SubscriptionHandler {
- return this.#gql.subscribeToQueryWithParser(
+ return this.#gql.subscribeToQueryWithParser(
queries.GET_SIGNER('query'),
{ signerId },
callback,
@@ -245,28 +245,30 @@ export default class AgreementConnectorTheGraph implements IAgreementConnector {
}
async collateralRequirement(
- disputableAppId: string
+ collateralRequirementId: string
): Promise {
return this.#gql.performQueryWithParser(
queries.GET_COLLATERAL_REQUIREMENT('query'),
- { disputableAppId },
+ { collateralRequirementId },
(result: QueryResult) => parseCollateralRequirement(result, this)
)
}
onCollateralRequirement(
- disputableAppId: string,
+ collateralRequirementId: string,
callback: SubscriptionCallback
): SubscriptionHandler {
return this.#gql.subscribeToQueryWithParser(
queries.GET_COLLATERAL_REQUIREMENT('subscription'),
- { disputableAppId },
+ { collateralRequirementId },
callback,
(result: QueryResult) => parseCollateralRequirement(result, this)
)
}
- async staking(stakingId: string): Promise {
+ async staking(
+ stakingId: string
+ ): Promise {
return this.#gql.performQueryWithParser(
queries.GET_STAKING('query'),
{ stakingId },
@@ -276,9 +278,9 @@ export default class AgreementConnectorTheGraph implements IAgreementConnector {
onStaking(
stakingId: string,
- callback: SubscriptionCallback
+ callback: SubscriptionCallback
): SubscriptionHandler {
- return this.#gql.subscribeToQueryWithParser(
+ return this.#gql.subscribeToQueryWithParser(
queries.GET_STAKING('query'),
{ stakingId },
callback,
@@ -288,29 +290,27 @@ export default class AgreementConnectorTheGraph implements IAgreementConnector {
async stakingMovements(
stakingId: string,
- agreement: string,
+ agreementId: string,
first: number,
skip: number
): Promise {
- const agreements = [agreement, '0x0000000000000000000000000000000000000000']
return this.#gql.performQueryWithParser(
queries.GET_STAKING_MOVEMENTS('query'),
- { stakingId, agreements, first, skip },
+ { stakingId, agreementId, first, skip },
(result: QueryResult) => parseStakingMovements(result, this)
)
}
onStakingMovements(
stakingId: string,
- agreement: string,
+ agreementId: string,
first: number,
skip: number,
callback: SubscriptionCallback
): SubscriptionHandler {
- const agreements = [agreement, '0x0000000000000000000000000000000000000000']
return this.#gql.subscribeToQueryWithParser(
queries.GET_STAKING_MOVEMENTS('query'),
- { stakingId, agreements, first, skip },
+ { stakingId, agreementId, first, skip },
callback,
(result: QueryResult) => parseStakingMovements(result, this)
)
diff --git a/packages/connect-agreement/src/thegraph/parsers/actions.ts b/packages/connect-agreement/src/thegraph/parsers/actions.ts
index cc21d404..b4762b7c 100644
--- a/packages/connect-agreement/src/thegraph/parsers/actions.ts
+++ b/packages/connect-agreement/src/thegraph/parsers/actions.ts
@@ -9,7 +9,7 @@ export function parseAction(result: QueryResult, connector: any): Action | null
return null
}
- const { id, agreement, collateralRequirement, disputable, version, disputableActionId, script, context, createdAt } = action
+ const { id, agreement, collateralRequirement, disputable, version, disputableActionId, context, createdAt } = action
return new Action(
{
@@ -19,7 +19,6 @@ export function parseAction(result: QueryResult, connector: any): Action | null
disputableId: disputable.id,
collateralRequirementId: collateralRequirement.id,
disputableActionId,
- script,
context,
createdAt
},
diff --git a/packages/connect-agreement/src/thegraph/parsers/collateralRequirements.ts b/packages/connect-agreement/src/thegraph/parsers/collateralRequirements.ts
index e6b77dc1..d074061d 100644
--- a/packages/connect-agreement/src/thegraph/parsers/collateralRequirements.ts
+++ b/packages/connect-agreement/src/thegraph/parsers/collateralRequirements.ts
@@ -20,6 +20,7 @@ function buildCollateralRequirement(
id,
disputableAppId: disputable.id,
tokenId: token.id,
+ tokenSymbol: token.symbol,
tokenDecimals: token.decimals,
actionAmount,
challengeAmount,
@@ -33,12 +34,11 @@ export function parseCollateralRequirement(
result: QueryResult,
connector: any
): CollateralRequirement {
- const disputable = result.data.disputable
+ const collateralRequirement = result.data.collateralRequirement
- if (!disputable || !disputable.currentCollateralRequirement) {
+ if (!collateralRequirement) {
throw new Error('Unable to parse collateral requirement.')
}
- const { currentCollateralRequirement } = disputable
- return buildCollateralRequirement(currentCollateralRequirement, connector)
+ return buildCollateralRequirement(collateralRequirement, connector)
}
diff --git a/packages/connect-agreement/src/thegraph/parsers/disputableApps.ts b/packages/connect-agreement/src/thegraph/parsers/disputableApps.ts
index a7589bfb..2f591677 100644
--- a/packages/connect-agreement/src/thegraph/parsers/disputableApps.ts
+++ b/packages/connect-agreement/src/thegraph/parsers/disputableApps.ts
@@ -17,7 +17,7 @@ function buildDisputableApp(disputableApp: any, connector: any): DisputableApp {
address,
activated,
agreementId: agreement.id,
- collateralRequirementId: currentCollateralRequirement.id,
+ currentCollateralRequirementId: currentCollateralRequirement.id,
}
return new DisputableApp(disputableAppData, connector)
diff --git a/packages/connect-agreement/src/thegraph/parsers/signers.ts b/packages/connect-agreement/src/thegraph/parsers/signers.ts
index 2294b924..5d013e6f 100644
--- a/packages/connect-agreement/src/thegraph/parsers/signers.ts
+++ b/packages/connect-agreement/src/thegraph/parsers/signers.ts
@@ -3,11 +3,11 @@ import { QueryResult } from '@aragon/connect-thegraph'
import Signer from '../../models/Signer'
import Signature from '../../models/Signature'
-export function parseSigner(result: QueryResult, connector: any): Signer {
+export function parseSigner(result: QueryResult, connector: any): Signer | null {
const signer = result.data.signer
if (!signer) {
- throw new Error('Unable to parse signer.')
+ return null
}
return new Signer(
diff --git a/packages/connect-agreement/src/thegraph/parsers/staking.ts b/packages/connect-agreement/src/thegraph/parsers/staking.ts
index 8ae643e3..d87bcc40 100644
--- a/packages/connect-agreement/src/thegraph/parsers/staking.ts
+++ b/packages/connect-agreement/src/thegraph/parsers/staking.ts
@@ -3,11 +3,11 @@ import { QueryResult } from '@aragon/connect-thegraph'
import Staking from '../../models/Staking'
import StakingMovement from '../../models/StakingMovement'
-export function parseStaking(result: QueryResult, connector: any): Staking {
+export function parseStaking(result: QueryResult, connector: any): Staking | null {
const staking = result.data.staking
if (!staking) {
- throw new Error('Unable to parse staking.')
+ return null
}
const { id, token, user, total, available, locked, challenged } = staking
@@ -21,6 +21,7 @@ export function parseStaking(result: QueryResult, connector: any): Staking {
locked,
challenged,
tokenId: token.id,
+ tokenSymbol: token.symbol,
tokenDecimals: token.decimals,
},
connector
@@ -49,9 +50,12 @@ export function parseStakingMovements(
createdAt,
stakingId: staking.id,
tokenId: staking.token.id,
+ tokenSymbol: staking.token.symbol,
tokenDecimals: staking.token.decimals,
- actionId: (action ? action.id : null),
- agreementId: (agreement ? agreement.id : null),
+ actionId: action.id,
+ disputableAddress: action.disputable.address,
+ disputableActionId: action.disputableActionId,
+ agreementId: agreement.id
},
connector
)
diff --git a/packages/connect-agreement/src/thegraph/queries/index.ts b/packages/connect-agreement/src/thegraph/queries/index.ts
index ef33da7d..a3af595a 100644
--- a/packages/connect-agreement/src/thegraph/queries/index.ts
+++ b/packages/connect-agreement/src/thegraph/queries/index.ts
@@ -101,20 +101,19 @@ export const GET_SIGNATURES = (type: string) => gql`
`
export const GET_COLLATERAL_REQUIREMENT = (type: string) => gql`
- ${type} CollateralRequirement($disputableAppId: String!) {
- disputable(id: $disputableAppId) {
- currentCollateralRequirement {
+ ${type} CollateralRequirement($collateralRequirementId: String!) {
+ collateralRequirement(id: $collateralRequirementId) {
+ id
+ actionAmount
+ challengeAmount
+ challengeDuration
+ disputable {
id
- actionAmount
- challengeAmount
- challengeDuration
- disputable {
- id
- }
- token {
- id
- decimals
- }
+ }
+ token {
+ id
+ symbol
+ decimals
}
}
}
@@ -127,6 +126,7 @@ export const GET_STAKING = (type: string) => gql`
user
token {
id
+ symbol
decimals
}
available
@@ -138,16 +138,17 @@ export const GET_STAKING = (type: string) => gql`
`
export const GET_STAKING_MOVEMENTS = (type: string) => gql`
- ${type} StakingMovements($stakingId: String!, $agreements: [String]!, $first: Int!, $skip: Int!) {
+ ${type} StakingMovements($stakingId: String!, $agreementId: String!, $first: Int!, $skip: Int!) {
stakingMovements(where: {
staking: $stakingId,
- agreementId_in: $agreements,
+ agreement: $agreementId,
}, orderBy: createdAt, orderDirection: asc, first: $first, skip: $skip) {
id
staking {
id
token {
id
+ symbol
decimals
}
}
@@ -156,6 +157,10 @@ export const GET_STAKING_MOVEMENTS = (type: string) => gql`
}
action {
id
+ disputableActionId
+ disputable {
+ address
+ }
}
amount
actionState
@@ -182,7 +187,6 @@ export const GET_ACTION = (type: string) => gql`
id
}
disputableActionId
- script
context
createdAt
}
diff --git a/packages/connect-agreement/src/types.ts b/packages/connect-agreement/src/types.ts
index 60643653..824f0686 100644
--- a/packages/connect-agreement/src/types.ts
+++ b/packages/connect-agreement/src/types.ts
@@ -35,13 +35,14 @@ export interface DisputableAppData {
address: string
agreementId: string
activated: boolean
- collateralRequirementId: string
+ currentCollateralRequirementId: string
}
export interface CollateralRequirementData {
id: string
disputableAppId: string
tokenId: string
+ tokenSymbol: string
tokenDecimals: string
actionAmount: string
challengeAmount: string
@@ -76,7 +77,6 @@ export interface ActionData {
collateralRequirementId: string
versionId: string
context: string
- script: string
createdAt: string
}
@@ -84,6 +84,7 @@ export interface StakingData {
id: string
user: string
tokenId: string
+ tokenSymbol: string
tokenDecimals: string
available: string
locked: string
@@ -94,11 +95,14 @@ export interface StakingData {
export interface StakingMovementData {
id: string
tokenId: string
+ tokenSymbol: string
tokenDecimals: string
stakingId: string
agreementId: string
amount: string
actionId: string
+ disputableAddress: string
+ disputableActionId: string
actionState: string
collateralState: string
createdAt: string
@@ -139,10 +143,10 @@ export interface IAgreementConnector {
skip: number,
callback: SubscriptionCallback
): SubscriptionHandler
- signer(signerId: string): Promise
+ signer(signerId: string): Promise
onSigner(
signerId: string,
- callback: SubscriptionCallback
+ callback: SubscriptionCallback
): SubscriptionHandler
signatures(
signerId: string,
@@ -155,9 +159,9 @@ export interface IAgreementConnector {
skip: number,
callback: SubscriptionCallback
): SubscriptionHandler
- collateralRequirement(disputableAppId: string): Promise
+ collateralRequirement(collateralRequirementId: string): Promise
onCollateralRequirement(
- disputableAppId: string,
+ collateralRequirementId: string,
callback: SubscriptionCallback
): SubscriptionHandler
action(actionId: string): Promise
@@ -165,10 +169,10 @@ export interface IAgreementConnector {
actionId: string,
callback: SubscriptionCallback
): SubscriptionHandler
- staking(stakingId: string): Promise
+ staking(stakingId: string): Promise
onStaking(
stakingId: string,
- callback: SubscriptionCallback
+ callback: SubscriptionCallback
): SubscriptionHandler
stakingMovements(
stakingId: string,
diff --git a/packages/connect-agreement/subgraph/abis/Staking.json b/packages/connect-agreement/subgraph/abis/Staking.json
index 1bcb510f..997f407c 100644
--- a/packages/connect-agreement/subgraph/abis/Staking.json
+++ b/packages/connect-agreement/subgraph/abis/Staking.json
@@ -1,979 +1,841 @@
-[
- {
- "anonymous": false,
- "inputs": [
- {
- "indexed": true,
- "internalType": "address",
- "name": "account",
- "type": "address"
- },
- {
- "indexed": true,
- "internalType": "address",
- "name": "lockManager",
- "type": "address"
- },
- {
- "indexed": false,
- "internalType": "uint256",
- "name": "allowance",
- "type": "uint256"
- },
- {
- "indexed": false,
- "internalType": "bool",
- "name": "increase",
- "type": "bool"
- }
- ],
- "name": "LockAllowanceChanged",
- "type": "event"
- },
- {
- "anonymous": false,
- "inputs": [
- {
- "indexed": true,
- "internalType": "address",
- "name": "account",
- "type": "address"
- },
- {
- "indexed": true,
- "internalType": "address",
- "name": "lockManager",
- "type": "address"
- },
- {
- "indexed": false,
- "internalType": "uint256",
- "name": "amount",
- "type": "uint256"
- },
- {
- "indexed": false,
- "internalType": "bool",
- "name": "increase",
- "type": "bool"
- }
- ],
- "name": "LockAmountChanged",
- "type": "event"
- },
- {
- "anonymous": false,
- "inputs": [
- {
- "indexed": true,
- "internalType": "address",
- "name": "account",
- "type": "address"
- },
- {
- "indexed": false,
- "internalType": "address",
- "name": "lockManager",
- "type": "address"
- }
- ],
- "name": "LockManagerRemoved",
- "type": "event"
- },
- {
- "anonymous": false,
- "inputs": [
- {
- "indexed": true,
- "internalType": "address",
- "name": "account",
- "type": "address"
- },
- {
- "indexed": true,
- "internalType": "address",
- "name": "oldLockManager",
- "type": "address"
- },
- {
- "indexed": false,
- "internalType": "address",
- "name": "newLockManager",
- "type": "address"
- }
- ],
- "name": "LockManagerTransferred",
- "type": "event"
- },
- {
- "anonymous": false,
- "inputs": [
- {
- "indexed": true,
- "internalType": "address",
- "name": "account",
- "type": "address"
- },
- {
- "indexed": true,
- "internalType": "address",
- "name": "lockManager",
- "type": "address"
- },
- {
- "indexed": false,
- "internalType": "bytes",
- "name": "data",
- "type": "bytes"
- }
- ],
- "name": "NewLockManager",
- "type": "event"
- },
- {
- "anonymous": false,
- "inputs": [
- {
- "indexed": true,
- "internalType": "address",
- "name": "from",
- "type": "address"
- },
- {
- "indexed": false,
- "internalType": "address",
- "name": "to",
- "type": "address"
- },
- {
- "indexed": false,
- "internalType": "uint256",
- "name": "amount",
- "type": "uint256"
- }
- ],
- "name": "StakeTransferred",
- "type": "event"
- },
- {
- "anonymous": false,
- "inputs": [
- {
- "indexed": true,
- "internalType": "address",
- "name": "user",
- "type": "address"
- },
- {
- "indexed": false,
- "internalType": "uint256",
- "name": "amount",
- "type": "uint256"
- },
- {
- "indexed": false,
- "internalType": "uint256",
- "name": "total",
- "type": "uint256"
- },
- {
- "indexed": false,
- "internalType": "bytes",
- "name": "data",
- "type": "bytes"
- }
- ],
- "name": "Staked",
- "type": "event"
- },
- {
- "anonymous": false,
- "inputs": [
- {
- "indexed": true,
- "internalType": "address",
- "name": "account",
- "type": "address"
- },
- {
- "indexed": true,
- "internalType": "address",
- "name": "lockManager",
- "type": "address"
- },
- {
- "indexed": false,
- "internalType": "uint256",
- "name": "amount",
- "type": "uint256"
- }
- ],
- "name": "Unlocked",
- "type": "event"
- },
- {
- "anonymous": false,
- "inputs": [
- {
- "indexed": true,
- "internalType": "address",
- "name": "user",
- "type": "address"
- },
- {
- "indexed": false,
- "internalType": "uint256",
- "name": "amount",
- "type": "uint256"
- },
- {
- "indexed": false,
- "internalType": "uint256",
- "name": "total",
- "type": "uint256"
- },
- {
- "indexed": false,
- "internalType": "bytes",
- "name": "data",
- "type": "bytes"
- }
- ],
- "name": "Unstaked",
- "type": "event"
- },
- {
- "constant": true,
- "inputs": [],
- "name": "getInitializationBlock",
- "outputs": [
- {
- "internalType": "uint256",
- "name": "",
- "type": "uint256"
- }
- ],
- "payable": false,
- "stateMutability": "view",
- "type": "function"
- },
- {
- "constant": true,
- "inputs": [],
- "name": "hasInitialized",
- "outputs": [
- {
- "internalType": "bool",
- "name": "",
- "type": "bool"
- }
- ],
- "payable": false,
- "stateMutability": "view",
- "type": "function"
- },
- {
- "constant": true,
- "inputs": [],
- "name": "isPetrified",
- "outputs": [
- {
- "internalType": "bool",
- "name": "",
- "type": "bool"
- }
- ],
- "payable": false,
- "stateMutability": "view",
- "type": "function"
- },
- {
- "constant": false,
- "inputs": [
- {
- "internalType": "contract ERC20",
- "name": "_stakingToken",
- "type": "address"
- }
- ],
- "name": "initialize",
- "outputs": [],
- "payable": false,
- "stateMutability": "nonpayable",
- "type": "function"
- },
- {
- "constant": false,
- "inputs": [
- {
- "internalType": "uint256",
- "name": "_amount",
- "type": "uint256"
- },
- {
- "internalType": "bytes",
- "name": "_data",
- "type": "bytes"
- }
- ],
- "name": "stake",
- "outputs": [],
- "payable": false,
- "stateMutability": "nonpayable",
- "type": "function"
- },
- {
- "constant": false,
- "inputs": [
- {
- "internalType": "address",
- "name": "_user",
- "type": "address"
- },
- {
- "internalType": "uint256",
- "name": "_amount",
- "type": "uint256"
- },
- {
- "internalType": "bytes",
- "name": "_data",
- "type": "bytes"
- }
- ],
- "name": "stakeFor",
- "outputs": [],
- "payable": false,
- "stateMutability": "nonpayable",
- "type": "function"
- },
- {
- "constant": false,
- "inputs": [
- {
- "internalType": "uint256",
- "name": "_amount",
- "type": "uint256"
- },
- {
- "internalType": "bytes",
- "name": "_data",
- "type": "bytes"
- }
- ],
- "name": "unstake",
- "outputs": [],
- "payable": false,
- "stateMutability": "nonpayable",
- "type": "function"
- },
- {
- "constant": false,
- "inputs": [
- {
- "internalType": "address",
- "name": "_lockManager",
- "type": "address"
- },
- {
- "internalType": "uint256",
- "name": "_allowance",
- "type": "uint256"
- },
- {
- "internalType": "bytes",
- "name": "_data",
- "type": "bytes"
- }
- ],
- "name": "allowManager",
- "outputs": [],
- "payable": false,
- "stateMutability": "nonpayable",
- "type": "function"
- },
- {
- "constant": false,
- "inputs": [
- {
- "internalType": "uint256",
- "name": "_amount",
- "type": "uint256"
- },
- {
- "internalType": "address",
- "name": "_lockManager",
- "type": "address"
- },
- {
- "internalType": "uint256",
- "name": "_allowance",
- "type": "uint256"
- },
- {
- "internalType": "bytes",
- "name": "_data",
- "type": "bytes"
- }
- ],
- "name": "allowManagerAndLock",
- "outputs": [],
- "payable": false,
- "stateMutability": "nonpayable",
- "type": "function"
- },
- {
- "constant": false,
- "inputs": [
- {
- "internalType": "address",
- "name": "_to",
- "type": "address"
- },
- {
- "internalType": "uint256",
- "name": "_amount",
- "type": "uint256"
- }
- ],
- "name": "transfer",
- "outputs": [],
- "payable": false,
- "stateMutability": "nonpayable",
- "type": "function"
- },
- {
- "constant": false,
- "inputs": [
- {
- "internalType": "address",
- "name": "_to",
- "type": "address"
- },
- {
- "internalType": "uint256",
- "name": "_amount",
- "type": "uint256"
- }
- ],
- "name": "transferAndUnstake",
- "outputs": [],
- "payable": false,
- "stateMutability": "nonpayable",
- "type": "function"
- },
- {
- "constant": false,
- "inputs": [
- {
- "internalType": "address",
- "name": "_from",
- "type": "address"
- },
- {
- "internalType": "address",
- "name": "_to",
- "type": "address"
- },
- {
- "internalType": "uint256",
- "name": "_amount",
- "type": "uint256"
- }
- ],
- "name": "slash",
- "outputs": [],
- "payable": false,
- "stateMutability": "nonpayable",
- "type": "function"
- },
- {
- "constant": false,
- "inputs": [
- {
- "internalType": "address",
- "name": "_from",
- "type": "address"
- },
- {
- "internalType": "address",
- "name": "_to",
- "type": "address"
- },
- {
- "internalType": "uint256",
- "name": "_amount",
- "type": "uint256"
- }
- ],
- "name": "slashAndUnstake",
- "outputs": [],
- "payable": false,
- "stateMutability": "nonpayable",
- "type": "function"
- },
- {
- "constant": false,
- "inputs": [
- {
- "internalType": "address",
- "name": "_from",
- "type": "address"
- },
- {
- "internalType": "address",
- "name": "_to",
- "type": "address"
- },
- {
- "internalType": "uint256",
- "name": "_unlockAmount",
- "type": "uint256"
- },
- {
- "internalType": "uint256",
- "name": "_slashAmount",
- "type": "uint256"
- }
- ],
- "name": "slashAndUnlock",
- "outputs": [],
- "payable": false,
- "stateMutability": "nonpayable",
- "type": "function"
- },
- {
- "constant": false,
- "inputs": [
- {
- "internalType": "address",
- "name": "_lockManager",
- "type": "address"
- },
- {
- "internalType": "uint256",
- "name": "_allowance",
- "type": "uint256"
- }
- ],
- "name": "increaseLockAllowance",
- "outputs": [],
- "payable": false,
- "stateMutability": "nonpayable",
- "type": "function"
- },
- {
- "constant": false,
- "inputs": [
- {
- "internalType": "address",
- "name": "_user",
- "type": "address"
- },
- {
- "internalType": "address",
- "name": "_lockManager",
- "type": "address"
- },
- {
- "internalType": "uint256",
- "name": "_allowance",
- "type": "uint256"
- }
- ],
- "name": "decreaseLockAllowance",
- "outputs": [],
- "payable": false,
- "stateMutability": "nonpayable",
- "type": "function"
- },
- {
- "constant": false,
- "inputs": [
- {
- "internalType": "address",
- "name": "_user",
- "type": "address"
- },
- {
- "internalType": "address",
- "name": "_lockManager",
- "type": "address"
- },
- {
- "internalType": "uint256",
- "name": "_amount",
- "type": "uint256"
- }
- ],
- "name": "lock",
- "outputs": [],
- "payable": false,
- "stateMutability": "nonpayable",
- "type": "function"
- },
- {
- "constant": false,
- "inputs": [
- {
- "internalType": "address",
- "name": "_user",
- "type": "address"
- },
- {
- "internalType": "address",
- "name": "_lockManager",
- "type": "address"
- },
- {
- "internalType": "uint256",
- "name": "_amount",
- "type": "uint256"
- }
- ],
- "name": "unlock",
- "outputs": [],
- "payable": false,
- "stateMutability": "nonpayable",
- "type": "function"
- },
- {
- "constant": false,
- "inputs": [
- {
- "internalType": "address",
- "name": "_user",
- "type": "address"
- },
- {
- "internalType": "address",
- "name": "_lockManager",
- "type": "address"
- }
- ],
- "name": "unlockAndRemoveManager",
- "outputs": [],
- "payable": false,
- "stateMutability": "nonpayable",
- "type": "function"
- },
- {
- "constant": false,
- "inputs": [
- {
- "internalType": "address",
- "name": "_user",
- "type": "address"
- },
- {
- "internalType": "address",
- "name": "_newLockManager",
- "type": "address"
- }
- ],
- "name": "setLockManager",
- "outputs": [],
- "payable": false,
- "stateMutability": "nonpayable",
- "type": "function"
- },
- {
- "constant": false,
- "inputs": [
- {
- "internalType": "address",
- "name": "_from",
- "type": "address"
- },
- {
- "internalType": "uint256",
- "name": "_amount",
- "type": "uint256"
- },
- {
- "internalType": "address",
- "name": "_token",
- "type": "address"
- },
- {
- "internalType": "bytes",
- "name": "_data",
- "type": "bytes"
- }
- ],
- "name": "receiveApproval",
- "outputs": [],
- "payable": false,
- "stateMutability": "nonpayable",
- "type": "function"
- },
- {
- "constant": true,
- "inputs": [],
- "name": "supportsHistory",
- "outputs": [
- {
- "internalType": "bool",
- "name": "",
- "type": "bool"
- }
- ],
- "payable": false,
- "stateMutability": "pure",
- "type": "function"
- },
- {
- "constant": true,
- "inputs": [],
- "name": "token",
- "outputs": [
- {
- "internalType": "address",
- "name": "",
- "type": "address"
- }
- ],
- "payable": false,
- "stateMutability": "view",
- "type": "function"
- },
- {
- "constant": true,
- "inputs": [
- {
- "internalType": "address",
- "name": "_user",
- "type": "address"
- }
- ],
- "name": "lastStakedFor",
- "outputs": [
- {
- "internalType": "uint256",
- "name": "",
- "type": "uint256"
- }
- ],
- "payable": false,
- "stateMutability": "view",
- "type": "function"
- },
- {
- "constant": true,
- "inputs": [
- {
- "internalType": "address",
- "name": "_user",
- "type": "address"
- }
- ],
- "name": "lockedBalanceOf",
- "outputs": [
- {
- "internalType": "uint256",
- "name": "",
- "type": "uint256"
- }
- ],
- "payable": false,
- "stateMutability": "view",
- "type": "function"
- },
- {
- "constant": true,
- "inputs": [
- {
- "internalType": "address",
- "name": "_user",
- "type": "address"
- },
- {
- "internalType": "address",
- "name": "_lockManager",
- "type": "address"
- }
- ],
- "name": "getLock",
- "outputs": [
- {
- "internalType": "uint256",
- "name": "_amount",
- "type": "uint256"
- },
- {
- "internalType": "uint256",
- "name": "_allowance",
- "type": "uint256"
- }
- ],
- "payable": false,
- "stateMutability": "view",
- "type": "function"
- },
- {
- "constant": true,
- "inputs": [
- {
- "internalType": "address",
- "name": "_user",
- "type": "address"
- }
- ],
- "name": "getBalancesOf",
- "outputs": [
- {
- "internalType": "uint256",
- "name": "staked",
- "type": "uint256"
- },
- {
- "internalType": "uint256",
- "name": "locked",
- "type": "uint256"
- }
- ],
- "payable": false,
- "stateMutability": "view",
- "type": "function"
- },
- {
- "constant": true,
- "inputs": [
- {
- "internalType": "address",
- "name": "_user",
- "type": "address"
- }
- ],
- "name": "totalStakedFor",
- "outputs": [
- {
- "internalType": "uint256",
- "name": "",
- "type": "uint256"
- }
- ],
- "payable": false,
- "stateMutability": "view",
- "type": "function"
- },
- {
- "constant": true,
- "inputs": [],
- "name": "totalStaked",
- "outputs": [
- {
- "internalType": "uint256",
- "name": "",
- "type": "uint256"
- }
- ],
- "payable": false,
- "stateMutability": "view",
- "type": "function"
- },
- {
- "constant": true,
- "inputs": [
- {
- "internalType": "address",
- "name": "_user",
- "type": "address"
- },
- {
- "internalType": "uint256",
- "name": "_blockNumber",
- "type": "uint256"
- }
- ],
- "name": "totalStakedForAt",
- "outputs": [
- {
- "internalType": "uint256",
- "name": "",
- "type": "uint256"
- }
- ],
- "payable": false,
- "stateMutability": "view",
- "type": "function"
- },
- {
- "constant": true,
- "inputs": [
- {
- "internalType": "uint256",
- "name": "_blockNumber",
- "type": "uint256"
- }
- ],
- "name": "totalStakedAt",
- "outputs": [
- {
- "internalType": "uint256",
- "name": "",
- "type": "uint256"
- }
- ],
- "payable": false,
- "stateMutability": "view",
- "type": "function"
- },
- {
- "constant": true,
- "inputs": [
- {
- "internalType": "address",
- "name": "_user",
- "type": "address"
- }
- ],
- "name": "unlockedBalanceOf",
- "outputs": [
- {
- "internalType": "uint256",
- "name": "",
- "type": "uint256"
- }
- ],
- "payable": false,
- "stateMutability": "view",
- "type": "function"
- },
- {
- "constant": true,
- "inputs": [
- {
- "internalType": "address",
- "name": "_sender",
- "type": "address"
- },
- {
- "internalType": "address",
- "name": "_user",
- "type": "address"
- },
- {
- "internalType": "address",
- "name": "_lockManager",
- "type": "address"
- },
- {
- "internalType": "uint256",
- "name": "_amount",
- "type": "uint256"
- }
- ],
- "name": "canUnlock",
- "outputs": [
- {
- "internalType": "bool",
- "name": "",
- "type": "bool"
- }
- ],
- "payable": false,
- "stateMutability": "view",
- "type": "function"
- }
-]
+{
+ "abi": [
+ {
+ "inputs": [
+ {
+ "internalType": "contract IERC20",
+ "name": "_token",
+ "type": "address"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "constructor"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "user",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "lockManager",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "allowance",
+ "type": "uint256"
+ }
+ ],
+ "name": "LockAllowanceChanged",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "user",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "lockManager",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ }
+ ],
+ "name": "LockAmountChanged",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "user",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "lockManager",
+ "type": "address"
+ }
+ ],
+ "name": "LockManagerRemoved",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "user",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "oldLockManager",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "newLockManager",
+ "type": "address"
+ }
+ ],
+ "name": "LockManagerTransferred",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "user",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "lockManager",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "bytes",
+ "name": "data",
+ "type": "bytes"
+ }
+ ],
+ "name": "NewLockManager",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "from",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ }
+ ],
+ "name": "StakeTransferred",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "user",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "total",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "bytes",
+ "name": "data",
+ "type": "bytes"
+ }
+ ],
+ "name": "Staked",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "user",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "total",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "bytes",
+ "name": "data",
+ "type": "bytes"
+ }
+ ],
+ "name": "Unstaked",
+ "type": "event"
+ },
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "token",
+ "outputs": [
+ {
+ "internalType": "contract IERC20",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "_amount",
+ "type": "uint256"
+ },
+ {
+ "internalType": "bytes",
+ "name": "_data",
+ "type": "bytes"
+ }
+ ],
+ "name": "stake",
+ "outputs": [],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_user",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_amount",
+ "type": "uint256"
+ },
+ {
+ "internalType": "bytes",
+ "name": "_data",
+ "type": "bytes"
+ }
+ ],
+ "name": "stakeFor",
+ "outputs": [],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "_amount",
+ "type": "uint256"
+ },
+ {
+ "internalType": "bytes",
+ "name": "_data",
+ "type": "bytes"
+ }
+ ],
+ "name": "unstake",
+ "outputs": [],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_lockManager",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_allowance",
+ "type": "uint256"
+ },
+ {
+ "internalType": "bytes",
+ "name": "_data",
+ "type": "bytes"
+ }
+ ],
+ "name": "allowManager",
+ "outputs": [],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_lockManager",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_allowance",
+ "type": "uint256"
+ }
+ ],
+ "name": "increaseLockAllowance",
+ "outputs": [],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_user",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "_lockManager",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_allowance",
+ "type": "uint256"
+ }
+ ],
+ "name": "decreaseLockAllowance",
+ "outputs": [],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_user",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_amount",
+ "type": "uint256"
+ }
+ ],
+ "name": "lock",
+ "outputs": [],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_user",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "_lockManager",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_amount",
+ "type": "uint256"
+ }
+ ],
+ "name": "unlock",
+ "outputs": [],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_user",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "_lockManager",
+ "type": "address"
+ }
+ ],
+ "name": "unlockAndRemoveManager",
+ "outputs": [],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_from",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "_to",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_amount",
+ "type": "uint256"
+ }
+ ],
+ "name": "slash",
+ "outputs": [],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_from",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "_to",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_amount",
+ "type": "uint256"
+ }
+ ],
+ "name": "slashAndUnstake",
+ "outputs": [],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_from",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "_to",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_unlockAmount",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_slashAmount",
+ "type": "uint256"
+ }
+ ],
+ "name": "slashAndUnlock",
+ "outputs": [],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_to",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_amount",
+ "type": "uint256"
+ }
+ ],
+ "name": "transfer",
+ "outputs": [],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_to",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_amount",
+ "type": "uint256"
+ }
+ ],
+ "name": "transferAndUnstake",
+ "outputs": [],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_from",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_amount",
+ "type": "uint256"
+ },
+ {
+ "internalType": "address",
+ "name": "_token",
+ "type": "address"
+ },
+ {
+ "internalType": "bytes",
+ "name": "_data",
+ "type": "bytes"
+ }
+ ],
+ "name": "receiveApproval",
+ "outputs": [],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "supportsHistory",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "pure",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_user",
+ "type": "address"
+ }
+ ],
+ "name": "lastStakedFor",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_user",
+ "type": "address"
+ }
+ ],
+ "name": "lockedBalanceOf",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_user",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "_lockManager",
+ "type": "address"
+ }
+ ],
+ "name": "getLock",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "allowance",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_user",
+ "type": "address"
+ }
+ ],
+ "name": "getBalancesOf",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "staked",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "locked",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_user",
+ "type": "address"
+ }
+ ],
+ "name": "totalStakedFor",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "totalStaked",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_user",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_blockNumber",
+ "type": "uint256"
+ }
+ ],
+ "name": "totalStakedForAt",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "_blockNumber",
+ "type": "uint256"
+ }
+ ],
+ "name": "totalStakedAt",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_user",
+ "type": "address"
+ }
+ ],
+ "name": "unlockedBalanceOf",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_sender",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "_user",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "_lockManager",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "_amount",
+ "type": "uint256"
+ }
+ ],
+ "name": "canUnlock",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/connect-agreement/subgraph/abis/StakingFactory.json b/packages/connect-agreement/subgraph/abis/StakingFactory.json
index 906f1ce0..b4ba4dc8 100644
--- a/packages/connect-agreement/subgraph/abis/StakingFactory.json
+++ b/packages/connect-agreement/subgraph/abis/StakingFactory.json
@@ -1,105 +1,86 @@
-[
- {
- "inputs": [],
- "payable": false,
- "stateMutability": "nonpayable",
- "type": "constructor"
- },
- {
- "anonymous": false,
- "inputs": [
- {
- "indexed": true,
- "internalType": "address",
- "name": "instance",
- "type": "address"
- },
- {
- "indexed": false,
- "internalType": "address",
- "name": "token",
- "type": "address"
- }
- ],
- "name": "NewStaking",
- "type": "event"
- },
- {
- "constant": true,
- "inputs": [],
- "name": "baseImplementation",
- "outputs": [
- {
- "internalType": "contract Staking",
- "name": "",
- "type": "address"
- }
- ],
- "payable": false,
- "stateMutability": "view",
- "type": "function"
- },
- {
- "constant": true,
- "inputs": [
- {
- "internalType": "contract ERC20",
- "name": "token",
- "type": "address"
- }
- ],
- "name": "existsInstance",
- "outputs": [
- {
- "internalType": "bool",
- "name": "",
- "type": "bool"
- }
- ],
- "payable": false,
- "stateMutability": "view",
- "type": "function"
- },
- {
- "constant": true,
- "inputs": [
- {
- "internalType": "contract ERC20",
- "name": "token",
- "type": "address"
- }
- ],
- "name": "getInstance",
- "outputs": [
- {
- "internalType": "contract Staking",
- "name": "",
- "type": "address"
- }
- ],
- "payable": false,
- "stateMutability": "view",
- "type": "function"
- },
- {
- "constant": false,
- "inputs": [
- {
- "internalType": "contract ERC20",
- "name": "token",
- "type": "address"
- }
- ],
- "name": "getOrCreateInstance",
- "outputs": [
- {
- "internalType": "contract Staking",
- "name": "",
- "type": "address"
- }
- ],
- "payable": false,
- "stateMutability": "nonpayable",
- "type": "function"
- }
-]
+{
+ "abi": [
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "instance",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "token",
+ "type": "address"
+ }
+ ],
+ "name": "NewStaking",
+ "type": "event"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "internalType": "contract IERC20",
+ "name": "_token",
+ "type": "address"
+ }
+ ],
+ "name": "existsInstance",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "internalType": "contract IERC20",
+ "name": "_token",
+ "type": "address"
+ }
+ ],
+ "name": "getOrCreateInstance",
+ "outputs": [
+ {
+ "internalType": "contract Staking",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "internalType": "contract IERC20",
+ "name": "_token",
+ "type": "address"
+ }
+ ],
+ "name": "getInstance",
+ "outputs": [
+ {
+ "internalType": "contract Staking",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/connect-agreement/subgraph/manifest/data/rinkeby-staging.json b/packages/connect-agreement/subgraph/manifest/data/rinkeby-staging.json
index 9664744d..135814d1 100644
--- a/packages/connect-agreement/subgraph/manifest/data/rinkeby-staging.json
+++ b/packages/connect-agreement/subgraph/manifest/data/rinkeby-staging.json
@@ -4,8 +4,8 @@
"Organizations": [
{
"name": "Agreement",
- "address": "0x51A41E43af0774565f0be5cEbC50C693CC19E4eE",
- "startBlock": "6921112"
+ "address": "0xfa5bae0029f0c2acffcc71ada4249bd0eaafd1f4",
+ "startBlock": "7200827"
}
],
"OrganizationFactories": [],
diff --git a/packages/connect-agreement/subgraph/schema.graphql b/packages/connect-agreement/subgraph/schema.graphql
index 1907c029..7f3541c0 100644
--- a/packages/connect-agreement/subgraph/schema.graphql
+++ b/packages/connect-agreement/subgraph/schema.graphql
@@ -39,7 +39,6 @@ type Action @entity {
disputable: Disputable!
actionId: BigInt!
disputableActionId: BigInt!
- script: Bytes
context: Bytes!
closed: Boolean!
submitter: Signer!
@@ -130,9 +129,8 @@ type StakingMovement @entity {
id: ID!
staking: Staking!
amount: BigInt!
- agreementId: Bytes!
- agreement: Agreement
- action: Action
+ agreement: Agreement!
+ action: Action!
actionState: StakingActionState!
collateralState: StakingCollateralState!
createdAt: BigInt!
@@ -142,8 +140,7 @@ enum StakingCollateralState {
Available,
Locked,
Challenged,
- Slashed,
- Withdrawn
+ Slashed
}
enum StakingActionState {
diff --git a/packages/connect-agreement/subgraph/src/Agreement.ts b/packages/connect-agreement/subgraph/src/Agreement.ts
index 8e45eeee..da5da4b2 100644
--- a/packages/connect-agreement/subgraph/src/Agreement.ts
+++ b/packages/connect-agreement/subgraph/src/Agreement.ts
@@ -1,13 +1,10 @@
-import { ethereum, BigInt, Address, log } from '@graphprotocol/graph-ts'
+import { ethereum, BigInt, Address } from '@graphprotocol/graph-ts'
import { ERC20 } from '../generated/schema'
import { ERC20 as ERC20Contract } from '../generated/templates/Agreement/ERC20'
-import { createAgreementStakingMovement } from './Staking'
-import { Staking as StakingTemplate } from '../generated/templates'
+import { Staking as StakingContract } from '../generated/templates/Agreement/Staking'
import { StakingFactory as StakingFactoryContract } from '../generated/templates/Agreement/StakingFactory'
-import { DisputableVoting as DisputableVotingTemplate } from '../generated/templates'
-import { DisputableAragonApp as DisputableAragonAppContract } from '../generated/templates/DisputableVoting/DisputableAragonApp'
-import { Agreement, Action, Signature, Version, Disputable, Challenge, Dispute, Evidence, Signer, CollateralRequirement, ArbitratorFee } from '../generated/schema'
+import { Agreement, Action, Signature, Version, Disputable, Challenge, Dispute, Evidence, Signer, CollateralRequirement, ArbitratorFee, StakingMovement, Staking } from '../generated/schema'
import {
Agreement as AgreementContract,
ActionSubmitted,
@@ -28,10 +25,6 @@ import {
/* eslint-disable @typescript-eslint/no-use-before-define */
-const DISPUTABLE_VOTING_OPEN = '0x705b5084c67966bb8e4640b28bab7a1e51e03d209d84e3a04d2a4f7415f93b34'
-const DISPUTABLE_VOTING_PRECEDENCE_CAMPAIGN = '0x39aa9e500efe56efda203714d12c78959ecbf71223162614ab5b56eaba014145'
-
-
export function handleSettingChanged(event: SettingChanged): void {
const agreementApp = AgreementContract.bind(event.address)
const settingData = agreementApp.getSetting(event.params.settingId)
@@ -102,7 +95,7 @@ export function handleActionSubmitted(event: ActionSubmitted): void {
action.createdAt = event.block.timestamp
action.save()
- createAgreementStakingMovement(event.address, event.params.actionId, 'new', event)
+ createStakingMovement(event.address, event.params.actionId, 'new', event)
}
export function handleActionClosed(event: ActionClosed): void {
@@ -110,7 +103,7 @@ export function handleActionClosed(event: ActionClosed): void {
action.closed = true
action.save()
- createAgreementStakingMovement(event.address, event.params.actionId, 'closed', event)
+ createStakingMovement(event.address, event.params.actionId, 'closed', event)
}
export function handleActionChallenged(event: ActionChallenged): void {
@@ -139,12 +132,12 @@ export function handleActionChallenged(event: ActionChallenged): void {
challenge.challengerArbitratorFee = challengerArbitratorFeeId
challenge.save()
- createAgreementStakingMovement(event.address, event.params.actionId, 'challenged', event)
+ createStakingMovement(event.address, event.params.actionId, 'challenged', event)
}
export function handleActionSettled(event: ActionSettled): void {
updateChallengeState(event.address, event.params.challengeId)
- createAgreementStakingMovement(event.address, event.params.actionId, 'settled', event)
+ createStakingMovement(event.address, event.params.actionId, 'settled', event)
}
export function handleActionDisputed(event: ActionDisputed): void {
@@ -177,7 +170,7 @@ export function handleActionVoided(event: ActionVoided): void {
export function handleActionRejected(event: ActionRejected): void {
updateChallengeState(event.address, event.params.challengeId)
updateDisputeState(event.address, event.params.challengeId, event)
- createAgreementStakingMovement(event.address, event.params.actionId, 'rejected', event)
+ createStakingMovement(event.address, event.params.actionId, 'rejected', event)
}
export function handleEvidenceSubmitted(event: EvidenceSubmitted): void {
@@ -220,27 +213,10 @@ function loadOrCreateDisputable(agreement: Address, disputableAddress: Address):
disputable = new Disputable(disputableId)
disputable.agreement = agreement.toHexString()
disputable.address = disputableAddress
- createDisputableTemplate(disputableAddress)
}
return disputable!
}
-function createDisputableTemplate(disputable: Address): void {
- const disputableApp = DisputableAragonAppContract.bind(disputable)
- const optionalAppId = disputableApp.try_appId()
-
- if (!optionalAppId.reverted) {
- const appId = optionalAppId.value.toHexString()
- if (appId == DISPUTABLE_VOTING_OPEN || appId == DISPUTABLE_VOTING_PRECEDENCE_CAMPAIGN) {
- DisputableVotingTemplate.create(disputable)
- } else {
- log.warning('Received unknown disputable app with app ID {}', [appId])
- }
- } else {
- log.warning('Received disputable app without app ID', [])
- }
-}
-
function loadOrCreateDispute(agreement: Address, challengeId: BigInt, event: ethereum.Event): Dispute {
const agreementApp = AgreementContract.bind(agreement)
const challengeData = agreementApp.getChallenge(challengeId)
@@ -294,6 +270,91 @@ function updateCollateralRequirement(agreement: Address, disputable: Address, co
requirement.save()
}
+function createStakingMovement(agreement: Address, actionId: BigInt, type: string, event: ethereum.Event): void {
+ const agreementApp = AgreementContract.bind(agreement)
+ const actionData = agreementApp.getAction(actionId)
+ const collateralData = agreementApp.getCollateralRequirement(actionData.value0, actionData.value2)
+
+ const user = actionData.value4
+ const token = collateralData.value0
+ const collateralAmount = collateralData.value2
+
+ if (collateralAmount.equals(BigInt.fromI32(0))) {
+ return
+ }
+
+ const factory = StakingFactoryContract.bind(agreementApp.stakingFactory())
+ const stakingAddress = factory.getInstance(token)
+ const staking = updateStaking(stakingAddress, token, user)
+
+ const id = buildStakingMovementId(token, user, buildId(event))
+ const movement = new StakingMovement(id)
+ movement.staking = staking.id
+ movement.agreement = agreement.toHexString()
+ movement.action = buildActionId(agreement, actionId)
+ movement.createdAt = event.block.timestamp
+
+ if (type == 'new') {
+ movement.amount = collateralAmount
+ movement.actionState = 'Scheduled'
+ movement.collateralState = 'Locked'
+ } else if (type == 'challenged') {
+ movement.amount = collateralAmount
+ movement.actionState = 'Challenged'
+ movement.collateralState = 'Challenged'
+ staking.challenged = staking.challenged.plus(collateralAmount)
+ } else if (type == 'settled') {
+ const challengeData = agreementApp.getChallenge(actionData.value7)
+ movement.amount = challengeData.value4
+ movement.actionState = 'Settled'
+ movement.collateralState = 'Slashed'
+ staking.challenged = staking.challenged.minus(collateralAmount)
+ } else if (type == 'rejected') {
+ movement.amount = collateralAmount
+ movement.actionState = 'Cancelled'
+ movement.collateralState = 'Slashed'
+ staking.challenged = staking.challenged.minus(collateralAmount)
+ } else { // closed
+ movement.amount = collateralAmount
+ movement.actionState = 'Completed'
+ movement.collateralState = 'Available'
+ staking.challenged = staking.challenged.minus(collateralAmount)
+ }
+
+ staking.save()
+ movement.save()
+}
+
+function updateStaking(stakingAddress: Address, token: Address, user: Address): Staking {
+ const stakingApp = StakingContract.bind(stakingAddress)
+ const balance = stakingApp.getBalancesOf(user)
+
+ const staking = loadOrCreateStaking(token, user)
+ staking.total = balance.value0
+ staking.locked = balance.value1
+ staking.available = staking.total.minus(staking.locked)
+ staking.save()
+
+ return staking
+}
+
+function loadOrCreateStaking(token: Address, user: Address): Staking {
+ const id = buildStakingId(token, user)
+ let staking = Staking.load(id)
+
+ if (staking === null) {
+ staking = new Staking(id)
+ staking.user = user
+ staking.token = token.toHexString()
+ staking.total = BigInt.fromI32(0)
+ staking.locked = BigInt.fromI32(0)
+ staking.available = BigInt.fromI32(0)
+ staking.challenged = BigInt.fromI32(0)
+ }
+
+ return staking!
+}
+
function createArbitratorFee(agreement: Address, id: string, feeToken: Address, feeAmount: BigInt): void {
const arbitratorFee = new ArbitratorFee(id)
arbitratorFee.amount = feeAmount
@@ -312,14 +373,6 @@ export function buildERC20(agreement: Address, address: Address): string {
token.symbol = tokenContract.symbol()
token.decimals = tokenContract.decimals()
token.save()
-
- const agreementApp = AgreementContract.bind(agreement)
- const stakingFactoryAddress = agreementApp.stakingFactory()
- const stakingFactory = StakingFactoryContract.bind(stakingFactoryAddress)
- const stakingAddress = stakingFactory.getInstance(Address.fromString(id))
- if (stakingAddress.toHexString() != '0x0000000000000000000000000000000000000000') {
- StakingTemplate.create(stakingAddress)
- }
}
return token.id
@@ -353,6 +406,14 @@ function buildCollateralRequirementId(agreement: Address, disputable: Address, c
return buildDisputableId(agreement, disputable) + "-collateral-requirement-" + collateralRequirementId.toString()
}
+function buildStakingId(token: Address, user: Address): string {
+ return token.toHexString() + "-staking-" + user.toHexString()
+}
+
+function buildStakingMovementId(token: Address, user: Address, id: string): string {
+ return buildStakingId(token, user) + "-movement-" + id
+}
+
function buildId(event: ethereum.Event): string {
return event.transaction.hash.toHexString() + event.logIndex.toString()
}
diff --git a/packages/connect-agreement/subgraph/src/DisputableAragonApp.ts b/packages/connect-agreement/subgraph/src/DisputableAragonApp.ts
deleted file mode 100644
index 245c5643..00000000
--- a/packages/connect-agreement/subgraph/src/DisputableAragonApp.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { Action } from '../generated/schema'
-import { buildActionId } from './Agreement'
-
-import {
- StartVote as StartVoteEvent,
- DisputableVoting as DisputableVotingContract
-} from '../generated/templates/DisputableVoting/DisputableVoting'
-
-
-export function handleStartVote(event: StartVoteEvent): void {
- const votingApp = DisputableVotingContract.bind(event.address)
- const agreement = votingApp.getAgreement()
- const voteData = votingApp.getVote(event.params.voteId)
-
- const actionId = buildActionId(agreement, voteData.value7)
- const action = Action.load(actionId)!
- action.script = event.params.executionScript
- action.save()
-}
diff --git a/packages/connect-agreement/subgraph/src/Staking.ts b/packages/connect-agreement/subgraph/src/Staking.ts
deleted file mode 100644
index 1849aae6..00000000
--- a/packages/connect-agreement/subgraph/src/Staking.ts
+++ /dev/null
@@ -1,178 +0,0 @@
-import { ethereum, Bytes, BigInt, Address } from '@graphprotocol/graph-ts'
-
-import { buildActionId } from './Agreement'
-import { Staking, StakingMovement } from '../generated/schema'
-import { Agreement as AgreementContract } from '../generated/templates/Agreement/Agreement'
-import {
- Staked as StakedEvent,
- Unstaked as UnstakedEvent,
- StakeTransferred as StakeTransferredEvent,
- Staking as StakingContract
-} from '../generated/templates/Staking/Staking'
-
-/* eslint-disable @typescript-eslint/no-use-before-define */
-
-
-export function handleStaked(event: StakedEvent): void {
- const stakingApp = StakingContract.bind(event.address)
- const token = stakingApp.token()
- const staking = updateStaking(event.address, token, event.params.user)
-
- const id = buildStakingMovementId(token, event.params.user, buildId(event))
- const movement = new StakingMovement(id)
- movement.amount = event.params.amount
- movement.staking = staking.id
- movement.actionState = 'NA'
- movement.collateralState = 'Available'
- movement.createdAt = event.block.timestamp
- movement.agreementId = Bytes.fromHexString('0x0000000000000000000000000000000000000000') as Bytes
- movement.save()
-}
-
-export function handleUnstaked(event: UnstakedEvent): void {
- const stakingApp = StakingContract.bind(event.address)
- const token = stakingApp.token()
- const staking = updateStaking(event.address, token, event.params.user)
-
- const id = buildStakingMovementId(token, event.params.user, buildId(event))
- const movement = new StakingMovement(id)
- movement.amount = event.params.amount
- movement.staking = staking.id
- movement.actionState = 'NA'
- movement.collateralState = 'Withdrawn'
- movement.createdAt = event.block.timestamp
- movement.agreementId = Bytes.fromHexString('0x0000000000000000000000000000000000000000') as Bytes
- movement.save()
-}
-
-export function handleStakeTransferred(event: StakeTransferredEvent): void {
- const stakingApp = StakingContract.bind(event.address)
- const token = stakingApp.token()
-
- const fromStaking = updateStaking(event.address, token, event.params.from)
- const withdrawId = buildStakingMovementId(token, event.params.from, buildId(event))
- const withdraw = new StakingMovement(withdrawId)
- withdraw.amount = event.params.amount
- withdraw.staking = fromStaking.id
- withdraw.actionState = 'NA'
- withdraw.collateralState = 'Withdrawn'
- withdraw.createdAt = event.block.timestamp
- withdraw.agreementId = Bytes.fromHexString('0x0000000000000000000000000000000000000000') as Bytes
- withdraw.save()
-
- const toStaking = updateStaking(event.address, token, event.params.to)
- const despositId = buildStakingMovementId(token, event.params.to, buildId(event))
- const deposit = new StakingMovement(despositId)
- deposit.amount = event.params.amount
- deposit.staking = toStaking.id
- deposit.actionState = 'NA'
- deposit.collateralState = 'Available'
- deposit.createdAt = event.block.timestamp
- deposit.agreementId = Bytes.fromHexString('0x0000000000000000000000000000000000000000') as Bytes
- deposit.save()
-}
-
-export function createAgreementStakingMovement(agreement: Address, actionId: BigInt, type: string, event: ethereum.Event): void {
- const agreementApp = AgreementContract.bind(agreement)
- const actionData = agreementApp.getAction(actionId)
- const collateralData = agreementApp.getCollateralRequirement(actionData.value0, actionData.value2)
-
- const user = actionData.value4
- const token = collateralData.value0
- const collateralAmount = collateralData.value2
-
- if (collateralAmount.equals(BigInt.fromI32(0))) {
- return
- }
-
- const staking = loadOrCreateStaking(token, user)
-
- const id = buildStakingMovementId(token, user, buildId(event))
- const movement = new StakingMovement(id)
- movement.staking = staking.id
- movement.agreement = agreement.toHexString()
- movement.agreementId = agreement
- movement.action = buildActionId(agreement, actionId)
- movement.createdAt = event.block.timestamp
-
- if (type == 'new') {
- movement.amount = collateralAmount
- movement.actionState = 'Scheduled'
- movement.collateralState = 'Locked'
- staking.available = staking.available.minus(collateralAmount)
- staking.locked = staking.locked.plus(collateralAmount)
- } else if (type == 'challenged') {
- movement.amount = collateralAmount
- movement.actionState = 'Challenged'
- movement.collateralState = 'Challenged'
- staking.challenged = staking.challenged.plus(collateralAmount)
- } else if (type == 'settled') {
- const challengeData = agreementApp.getChallenge(actionData.value7)
- const settlementOffer = challengeData.value4
- const unlockedBalance = collateralAmount.minus(settlementOffer)
- movement.amount = settlementOffer
- movement.actionState = 'Settled'
- movement.collateralState = 'Slashed'
- staking.available = staking.available.plus(unlockedBalance)
- staking.locked = staking.locked.minus(collateralAmount)
- staking.challenged = staking.challenged.minus(collateralAmount)
- } else if (type == 'rejected') {
- movement.amount = collateralAmount
- movement.actionState = 'Cancelled'
- movement.collateralState = 'Slashed'
- staking.locked = staking.locked.minus(collateralAmount)
- staking.challenged = staking.challenged.minus(collateralAmount)
- } else { // closed
- movement.amount = collateralAmount
- movement.actionState = 'Completed'
- movement.collateralState = 'Available'
- staking.available = staking.available.plus(collateralAmount)
- staking.locked = staking.locked.minus(collateralAmount)
- staking.challenged = staking.challenged.minus(collateralAmount)
- }
-
- staking.save()
- movement.save()
-}
-
-function updateStaking(stakingAddress: Address, token: Address, user: Address): Staking {
- const stakingApp = StakingContract.bind(stakingAddress)
- const balance = stakingApp.getBalancesOf(user)
-
- const staking = loadOrCreateStaking(token, user)
- staking.total = balance.value0
- staking.locked = balance.value1
- staking.available = staking.total.minus(staking.locked)
- staking.save()
-
- return staking
-}
-
-function loadOrCreateStaking(token: Address, user: Address): Staking {
- const id = buildStakingId(token, user)
- let staking = Staking.load(id)
-
- if (staking === null) {
- staking = new Staking(id)
- staking.user = user
- staking.token = token.toHexString()
- staking.total = BigInt.fromI32(0)
- staking.locked = BigInt.fromI32(0)
- staking.available = BigInt.fromI32(0)
- staking.challenged = BigInt.fromI32(0)
- }
-
- return staking!
-}
-
-function buildStakingId(token: Address, user: Address): string {
- return token.toHexString() + "-user-" + user.toHexString()
-}
-
-function buildStakingMovementId(token: Address, user: Address, id: string): string {
- return buildStakingId(token, user) + "-movement-" + id
-}
-
-function buildId(event: ethereum.Event): string {
- return event.transaction.hash.toHexString() + event.logIndex.toString()
-}
diff --git a/packages/connect-agreement/subgraph/subgraph.template.yaml b/packages/connect-agreement/subgraph/subgraph.template.yaml
index b13a1d94..fb414ffb 100644
--- a/packages/connect-agreement/subgraph/subgraph.template.yaml
+++ b/packages/connect-agreement/subgraph/subgraph.template.yaml
@@ -68,49 +68,3 @@ templates:
- event: Signed(indexed address,uint256)
handler: handleSigned
file: ./src/Agreement.ts
- - kind: ethereum/contract
- name: DisputableVoting
- network: {{network}}
- source:
- abi: DisputableVoting
- mapping:
- kind: ethereum/events
- apiVersion: 0.0.3
- language: wasm/assemblyscript
- entities:
- - Agreement
- - Disputable
- abis:
- - name: DisputableVoting
- file: ./abis/DisputableVoting.json
- - name: DisputableAragonApp
- file: ./abis/DisputableAragonApp.json
- eventHandlers:
- - event: StartVote(indexed uint256,indexed address,bytes,bytes)
- handler: handleStartVote
- file: ./src/DisputableAragonApp.ts
- - kind: ethereum/contract
- name: Staking
- network: {{network}}
- source:
- abi: Staking
- mapping:
- kind: ethereum/events
- apiVersion: 0.0.3
- language: wasm/assemblyscript
- entities:
- - Staking
- - ERC20
- abis:
- - name: Staking
- file: ./abis/Staking.json
- - name: ERC20
- file: ./abis/MiniMeToken.json
- eventHandlers:
- - event: Staked(indexed address,uint256,uint256,bytes)
- handler: handleStaked
- - event: Unstaked(indexed address,uint256,uint256,bytes)
- handler: handleUnstaked
- - event: StakeTransferred(indexed address,address,uint256)
- handler: handleStakeTransferred
- file: ./src/Staking.ts
diff --git a/packages/connect-core/package.json b/packages/connect-core/package.json
index b247363f..b962aa02 100644
--- a/packages/connect-core/package.json
+++ b/packages/connect-core/package.json
@@ -1,6 +1,6 @@
{
"name": "@aragon/connect-core",
- "version": "0.7.0",
+ "version": "0.8.0-alpha.6",
"license": "LGPL-3.0-or-later",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
diff --git a/packages/connect-core/src/entities/App.ts b/packages/connect-core/src/entities/App.ts
index 12a2af3c..1cc1cd0b 100644
--- a/packages/connect-core/src/entities/App.ts
+++ b/packages/connect-core/src/entities/App.ts
@@ -1,37 +1,28 @@
-import Organization from './Organization'
-import Repo from './Repo'
-import Role from './Role'
+import {
+ Contract,
+ providers as ethersProvider,
+ utils as ethersUtils,
+} from 'ethers'
+
+import { appIntent } from '../utils/intent'
import {
Abi,
- AppIntent,
AragonArtifact,
AragonManifest,
- ConnectionContext,
Metadata,
+ AppData,
+ PathOptions,
} from '../types'
+import ForwardingPath from './ForwardingPath'
+import Organization from './Organization'
+import Repo from './Repo'
+import Role from './Role'
import { resolveArtifact, resolveManifest } from '../utils/metadata'
import IOrganizationConnector from '../connections/IOrganizationConnector'
// TODO:
// [ ] (ipfs) contentUrl String The HTTP URL of the app content. Uses the IPFS HTTP provider. E.g. http://gateway.ipfs.io/ipfs/QmdLEDDfi…/ (ContentUri passing through the resolver)
-export interface AppData {
- address: string
- appId: string
- artifact?: string
- codeAddress: string
- contentUri?: string
- isForwarder?: boolean
- isUpgradeable?: boolean
- kernelAddress: string
- manifest?: string
- name?: string
- registry?: string
- registryAddress: string
- repoAddress?: string
- version?: string
-}
-
export default class App {
#metadata: Metadata
readonly address: string
@@ -75,12 +66,8 @@ export default class App {
return this.organization.connection.orgConnector
}
- async repo(): Promise {
- return this.orgConnector().repoForApp(this.organization, this.address)
- }
-
- async roles(): Promise {
- return this.orgConnector().rolesForAddress(this.organization, this.address)
+ get provider(): ethersProvider.Provider {
+ return this.organization.connection.ethersProvider
}
get artifact(): AragonArtifact {
@@ -95,16 +82,12 @@ export default class App {
return this.artifact.abi
}
- get intents(): AppIntent[] {
- return this.artifact.functions
- }
-
- get deprecatedIntents(): { [version: string]: AppIntent[] } {
- return this.artifact.deprecatedFunctions
+ async repo(): Promise {
+ return this.orgConnector().repoForApp(this.organization, this.address)
}
- get appName(): string {
- return this.artifact.appName
+ async roles(): Promise {
+ return this.orgConnector().rolesForAddress(this.organization, this.address)
}
toJSON() {
@@ -115,4 +98,55 @@ export default class App {
organization: null,
}
}
+
+ ethersContract(): Contract {
+ if (!this.abi) {
+ throw new Error(
+ `No ABI specified in app for ${this.address}. Make sure the metada for the app is available`
+ )
+ }
+ return new Contract(this.address, this.abi, this.provider)
+ }
+
+ ethersInterface(): ethersUtils.Interface {
+ if (!this.abi) {
+ throw new Error(
+ `No ABI specified in app for ${this.address}. Make sure the metada for the app is available`
+ )
+ }
+ return new ethersUtils.Interface(this.abi)
+ }
+
+ /**
+ * Calculate the forwarding path for an app action
+ * that invokes `methodSignature` with `params`.
+ *
+ * @param {string} methodSignature
+ * @param {Array<*>} params
+ * @param {Object} options
+ * @return {Promise} An object that represents the forwarding path corresponding to an action.
+ */
+ async intent(
+ methodSignature: string,
+ params: any[],
+ options: PathOptions = {}
+ ): Promise {
+ const sender = options.actAs || this.organization.connection.actAs
+ if (!sender) {
+ throw new Error(
+ `No sender address specified. Use 'actAs' option or set one as default on your organization connection.`
+ )
+ }
+
+ const installedApps = await this.organization.apps()
+
+ return appIntent(
+ sender,
+ this,
+ methodSignature,
+ params,
+ installedApps,
+ this.provider
+ )
+ }
}
diff --git a/packages/connect-core/src/entities/ForwardingPath.ts b/packages/connect-core/src/entities/ForwardingPath.ts
new file mode 100644
index 00000000..93caf02c
--- /dev/null
+++ b/packages/connect-core/src/entities/ForwardingPath.ts
@@ -0,0 +1,84 @@
+import { providers as ethersProviders } from 'ethers'
+
+import { buildApprovePreTransactions } from '../utils/transactions'
+import {
+ ForwardingPathData,
+ StepDescribed,
+ TokenData,
+ TransactionData,
+} from '../types'
+import ForwardingPathDescription, {
+ describePath,
+} from '../utils/descriptor/index'
+import App from './App'
+import Transaction from './Transaction'
+
+const normalizePreTransactions = (preTransactions: (Transaction | TransactionData)[]): Transaction[] => {
+ return preTransactions.map((preTransaction: Transaction | TransactionData) =>
+ (preTransaction instanceof Transaction) ? preTransaction : new Transaction(preTransaction)
+ )
+}
+
+export default class ForwardingPath {
+ #installedApps: App[]
+ #provider: ethersProviders.Provider
+ readonly destination: App
+ readonly path: Transaction[]
+ readonly transactions: Transaction[]
+
+ constructor(
+ data: ForwardingPathData,
+ installedApps: App[],
+ provider: ethersProviders.Provider
+ ) {
+ this.#installedApps = installedApps
+ this.#provider = provider
+ this.destination = data.destination
+ this.path = data.path
+ this.transactions = data.transactions
+ }
+
+ // Lets consumers pass a callback to sign any number of transactions.
+ // This is similar to calling transactions() and using a loop, but shorter.
+ // It returns the value returned by the library, usually a transaction receipt.
+ async sign(
+ callback: (tx: Transaction) => Promise
+ ): Promise {
+ return Promise.all(this.transactions.map(async (tx) => await callback(tx)))
+ }
+
+ // Return a description of the forwarding path, to be rendered.
+ async describe(): Promise {
+ let description: StepDescribed[] = []
+ if (this.path.length > 0) {
+ try {
+ description = await describePath(
+ this.path,
+ this.#installedApps,
+ this.#provider
+ )
+ } catch (_) {}
+ }
+
+ return new ForwardingPathDescription(description, this.#installedApps)
+ }
+
+ // Build a token allowance pre-transaction
+ async buildApprovePreTransactions(
+ tokenData: TokenData
+ ): Promise {
+ return buildApprovePreTransactions(
+ this.transactions[0],
+ tokenData,
+ this.#provider
+ )
+ }
+
+ // Apply a pretransaction to the path
+ applyPreTransactions(preTransactions: (Transaction | TransactionData)[]): void {
+ normalizePreTransactions(preTransactions)
+ .reverse()
+ .filter(preTransaction => !!preTransaction)
+ .forEach(preTransaction => this.transactions.unshift(preTransaction))
+ }
+}
diff --git a/packages/connect-core/src/entities/Organization.ts b/packages/connect-core/src/entities/Organization.ts
index 32c0ebf3..9ed73530 100644
--- a/packages/connect-core/src/entities/Organization.ts
+++ b/packages/connect-core/src/entities/Organization.ts
@@ -5,17 +5,20 @@ import {
SubscriptionCallback,
SubscriptionResult,
} from '@aragon/connect-types'
-import { ConnectionContext } from '../types'
+
+import ForwardingPathDescription, {
+ decodeForwardingPath,
+ describePath,
+ describeTransaction,
+} from '../utils/descriptor/index'
+import { ConnectionContext, PostProcessDescription } from '../types'
import { ErrorInvalidLocation } from '../errors'
-import {
- isAddress,
- normalizeFiltersAndCallback,
- subscription,
- toArrayEntry,
-} from '../utils'
-import TransactionIntent from '../transactions/TransactionIntent'
+import { isAddress } from '../utils/address'
+import { normalizeFiltersAndCallback, toArrayEntry } from '../utils/misc'
+import { subscription } from '../utils/subscriptions'
import App from './App'
import Permission from './Permission'
+import Transaction from './Transaction'
// TODO
// Organization#addApp(repoName, options)
@@ -77,6 +80,14 @@ export default class Organization {
return this.connection
}
+ //////// ACCOUNT /////////
+
+ actAss(sender: Address): void {
+ this.connection.actAs = sender
+ }
+
+ ///////// APPS ///////////
+
async app(filters?: AppFiltersParam): Promise {
return this.connection.orgConnector.appForOrg(
this,
@@ -127,6 +138,16 @@ export default class Organization {
)
}
+ async acl(): Promise {
+ return this.app('acl')
+ }
+
+ async kernel(): Promise {
+ return this.app('kernel')
+ }
+
+ ///////// PERMISSIONS ///////////
+
async permissions(): Promise {
return this.connection.orgConnector.permissionsForOrg(this)
}
@@ -139,14 +160,28 @@ export default class Organization {
)
}
- appIntent(
- appAddress: Address,
- functionName: string,
- functionArgs: any[]
- ): TransactionIntent {
- return new TransactionIntent(
- { contractAddress: appAddress, functionName, functionArgs },
- this,
+ //////// DESCRIPTIONS /////////
+
+ // Return a description of the forwarding path encoded on the evm script
+ async describeScript(script: string): Promise {
+ const installedApps = await this.apps()
+
+ const describedSteps = await describePath(
+ decodeForwardingPath(script),
+ installedApps,
+ this.connection.ethersProvider
+ )
+
+ return new ForwardingPathDescription(describedSteps, installedApps)
+ }
+
+ // Try to describe a single transaction using Radspec on the context of the organization
+ async describeTransaction(
+ transaction: Transaction
+ ): Promise {
+ return describeTransaction(
+ transaction,
+ await this.apps(),
this.connection.ethersProvider
)
}
diff --git a/packages/connect-core/src/entities/Permission.ts b/packages/connect-core/src/entities/Permission.ts
index 7e6a73e7..258fb8c5 100644
--- a/packages/connect-core/src/entities/Permission.ts
+++ b/packages/connect-core/src/entities/Permission.ts
@@ -1,21 +1,8 @@
-import IOrganizationConnector from '../connections/IOrganizationConnector'
+import { PermissionData, ParamData } from '../types'
import App from './App'
import Organization from './Organization'
import Role from './Role'
-
-export interface ParamData {
- argumentId: number
- operationType: number
- argumentValue: BigInt
-}
-
-export interface PermissionData {
- allowed: boolean
- appAddress: string
- granteeAddress: string
- params: ParamData[]
- roleHash: string
-}
+import IOrganizationConnector from '../connections/IOrganizationConnector'
export default class Permission implements PermissionData {
#organization: Organization
diff --git a/packages/connect-core/src/entities/Repo.ts b/packages/connect-core/src/entities/Repo.ts
index 41e2c9c3..60467e92 100644
--- a/packages/connect-core/src/entities/Repo.ts
+++ b/packages/connect-core/src/entities/Repo.ts
@@ -2,26 +2,17 @@ import {
AragonArtifact,
AragonArtifactRole,
AragonManifest,
- ConnectionContext,
Metadata,
+ RepoData,
} from '../types'
-import { resolveArtifact, resolveManifest } from '../utils/metadata'
import Organization from './Organization'
-
-export interface RepoData {
- address: string
- artifact?: string
- contentUri?: string
- manifest?: string
- name: string
- registry?: string
- registryAddress?: string
-}
+import { resolveArtifact, resolveManifest } from '../utils/metadata'
export default class Repo {
#metadata!: Metadata
readonly address: string
readonly contentUri?: string
+ readonly lastVersion?: string
readonly name: string
readonly registry?: string
readonly registryAddress?: string
@@ -30,6 +21,7 @@ export default class Repo {
this.#metadata = metadata
this.address = data.address
this.contentUri = data.contentUri
+ this.lastVersion = data.lastVersion
this.name = data.name
this.registry = data.registry
this.registryAddress = data.registryAddress
diff --git a/packages/connect-core/src/entities/Role.ts b/packages/connect-core/src/entities/Role.ts
index e3dc4aea..95d4eed1 100644
--- a/packages/connect-core/src/entities/Role.ts
+++ b/packages/connect-core/src/entities/Role.ts
@@ -1,17 +1,7 @@
-import { AragonArtifact, ConnectionContext, Metadata } from '../types'
import { resolveArtifact } from '../utils/metadata'
+import { AragonArtifact, Metadata, RoleData } from '../types'
import Organization from './Organization'
-import Permission, { PermissionData } from './Permission'
-
-export interface RoleData {
- appAddress: string
- appId: string
- artifact?: string
- contentUri?: string
- hash: string
- manager?: string
- grantees?: PermissionData[] | null
-}
+import Permission from './Permission'
export default class Role {
readonly appAddress!: string
diff --git a/packages/connect-core/src/entities/Transaction.ts b/packages/connect-core/src/entities/Transaction.ts
new file mode 100644
index 00000000..00397357
--- /dev/null
+++ b/packages/connect-core/src/entities/Transaction.ts
@@ -0,0 +1,24 @@
+import { Address } from '@aragon/connect-types'
+
+import { TransactionData } from '../types'
+
+export default class Transaction {
+ readonly data: string
+ readonly from: Address
+ readonly to: Address
+
+ constructor(data: TransactionData) {
+ if (!data.to) {
+ throw new Error(`Could not cosntruct trasanction: missing 'to'`)
+ }
+ if (!data.from) {
+ throw new Error(`Could not cosntruct trasanction: missing 'from'`)
+ }
+ if (!data.data) {
+ throw new Error(`Could not cosntruct trasanction: missing 'data'`)
+ }
+ this.data = data.data
+ this.from = data.from
+ this.to = data.to
+ }
+}
diff --git a/packages/connect-core/src/index.ts b/packages/connect-core/src/index.ts
index 74b10912..13b330f0 100644
--- a/packages/connect-core/src/index.ts
+++ b/packages/connect-core/src/index.ts
@@ -6,16 +6,26 @@ export type {
Networkish,
SubscriptionHandler,
} from '@aragon/connect-types'
+
export { default as IOrganizationConnector } from './connections/IOrganizationConnector'
export {
default as ConnectorJson,
ConnectorJsonConfig,
} from './connections/ConnectorJson'
-export { default as App, AppData } from './entities/App'
+export { default as App } from './entities/App'
+export { default as ForwardingPath } from './entities/ForwardingPath'
export { default as Organization } from './entities/Organization'
-export { default as Permission, PermissionData } from './entities/Permission'
-export { default as Repo, RepoData } from './entities/Repo'
-export { default as Role, RoleData } from './entities/Role'
-export { ConnectionContext, IpfsResolver } from './types'
+export { default as Permission } from './entities/Permission'
+export { default as Repo } from './entities/Repo'
+export { default as Role } from './entities/Role'
+export {
+ ConnectionContext,
+ IpfsResolver,
+ AppData,
+ ForwardingPathData,
+ PermissionData,
+ RepoData,
+ RoleData,
+} from './types'
export * from './utils'
export * from './errors'
diff --git a/packages/connect-core/src/transactions/TransactionIntent.ts b/packages/connect-core/src/transactions/TransactionIntent.ts
deleted file mode 100644
index 19700cb5..00000000
--- a/packages/connect-core/src/transactions/TransactionIntent.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-import { providers as ethersProviders } from 'ethers'
-import TransactionPath from './TransactionPath'
-import TransactionRequest from './TransactionRequest'
-import Organization from '../entities/Organization'
-import { calculateTransactionPath } from '../utils/path/calculatePath'
-import { describeTransactionPath } from '../utils/descriptions'
-
-export interface TransactionIntentData {
- contractAddress: string
- functionName: string
- functionArgs: any[]
-}
-
-export default class TransactionIntent {
- readonly contractAddress!: string
- readonly functionName!: string
- readonly functionArgs!: any[]
-
- #org: Organization
- #provider: ethersProviders.Provider
-
- constructor(
- data: TransactionIntentData,
- org: Organization,
- provider: ethersProviders.Provider
- ) {
- this.#org = org
- this.#provider = provider
-
- this.contractAddress = data.contractAddress
- this.functionArgs = data.functionArgs
- this.functionName = data.functionName
- }
-
- async paths(
- account: string,
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- options?: { as?: string; path?: string[] }
- ): Promise {
- const apps = await this.#org.apps()
-
- const {
- forwardingFeePretransaction,
- path,
- } = await calculateTransactionPath(
- account,
- this.contractAddress,
- this.functionName,
- this.functionArgs,
- apps,
- this.#provider
- )
-
- const describedPath = await describeTransactionPath(
- path,
- apps,
- this.#provider
- )
-
- return new TransactionPath({
- apps: apps.filter((app) =>
- path
- .map((transaction) => transaction.to)
- .some((address) => address === app.address)
- ),
- destination: apps.find((app) => app.address == this.contractAddress)!,
- forwardingFeePretransaction,
- transactions: describedPath,
- })
- }
-
- async transactions(
- account: string,
- options?: { as: string; path?: string[] }
- ): Promise {
- return (await this.paths(account, options)).transactions
- }
-}
diff --git a/packages/connect-core/src/transactions/TransactionPath.ts b/packages/connect-core/src/transactions/TransactionPath.ts
deleted file mode 100644
index 911277b9..00000000
--- a/packages/connect-core/src/transactions/TransactionPath.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import TransactionRequest from './TransactionRequest'
-import App from '../entities/App'
-
-export interface TransactionPathData {
- apps: App[]
- destination: App
- forwardingFeePretransaction?: TransactionRequest
- transactions: TransactionRequest[]
-}
-
-export default class TransactionPath {
- readonly apps!: App[]
- readonly destination!: App
- readonly forwardingFeePretransaction?: TransactionRequest
- readonly transactions!: TransactionRequest[]
-
- constructor(data: TransactionPathData) {
- this.apps = data.apps
- this.destination = data.destination
- this.forwardingFeePretransaction = data.forwardingFeePretransaction
- this.transactions = data.transactions
- }
-}
diff --git a/packages/connect-core/src/transactions/TransactionRequest.ts b/packages/connect-core/src/transactions/TransactionRequest.ts
deleted file mode 100644
index 01d7d1fc..00000000
--- a/packages/connect-core/src/transactions/TransactionRequest.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-export interface TransactionRequestData {
- children?: TransactionRequest[]
- description?: string
- descriptionAnnotated?: Annotation[]
- data: string
- from?: string
- to: string
-}
-
-export interface Annotation {
- type: string
- value: any
-}
-
-export default class TransactionRequest {
- readonly children?: TransactionRequest[]
- readonly description?: string
- readonly descriptionAnnotated?: Annotation[]
- readonly data!: string
- readonly from?: string
- readonly to!: string
-
- constructor(data: TransactionRequestData) {
- this.children = data.children
- this.description = data.description
- this.descriptionAnnotated = data.descriptionAnnotated
- this.data = data.data
- this.from = data.from
- this.to = data.to
- }
-}
diff --git a/packages/connect-core/src/types.ts b/packages/connect-core/src/types.ts
index 01b1ba75..30c36730 100644
--- a/packages/connect-core/src/types.ts
+++ b/packages/connect-core/src/types.ts
@@ -1,15 +1,140 @@
-import { providers as ethersProviders, utils as ethersUtils } from 'ethers'
+import {
+ BigNumber,
+ providers as ethersProviders,
+ utils as ethersUtils,
+} from 'ethers'
import { Address, Network } from '@aragon/connect-types'
+
import IOrganizationConnector from './connections/IOrganizationConnector'
+import App from './entities/App'
+import Transaction from './entities/Transaction'
-export type Metadata = (AragonArtifact | AragonManifest)[]
+export type Abi = (ethersUtils.EventFragment | ethersUtils.FunctionFragment)[]
-// Type definition: https://github.com/ethers-io/ethers.js/blob/ethers-v5-beta/packages/abi/lib/fragments.d.ts#L68
-export type FunctionFragment = ethersUtils.FunctionFragment
+export type AppOrAddress = App | Address
-export type Abi = (ethersUtils.EventFragment | ethersUtils.FunctionFragment)[]
+export type ForwardingPathDeclaration = AppOrAddress[]
+
+export type PathOptions = {
+ // The account to sign the transactions with. It is optional
+ // when `actAs` has been set with the connection. If not,
+ // the address has to be passed.
+ actAs?: Address
+
+ // Optionally declare a forwarding path. When not specified,
+ // the shortest path is used instead.
+ path?: ForwardingPathDeclaration
+}
+
+export interface CallScriptAction {
+ to: string
+ data: string
+}
+
+export interface DescriptionAnnotation {
+ type: string
+ value: any
+}
+
+export interface PostProcessDescription {
+ description: string
+ annotatedDescription?: DescriptionAnnotation[]
+}
+
+export interface StepDecoded {
+ to: string
+ data: string
+ from?: string
+ children?: StepDecoded[]
+}
+
+export interface StepDescribed extends StepDecoded {
+ description: string
+ annotatedDescription?: DescriptionAnnotation[]
+}
+
+export interface TransactionPath {
+ path: Transaction[]
+ transactions: Transaction[]
+}
+
+////// ENTITES /////
+
+export interface AppData {
+ address: string
+ appId: string
+ artifact?: string | null
+ codeAddress: string
+ contentUri?: string
+ isForwarder?: boolean
+ isUpgradeable?: boolean
+ kernelAddress: string
+ manifest?: string | null
+ name?: string
+ registry?: string
+ registryAddress: string
+ repoAddress?: string
+ version?: string
+}
+
+export interface ForwardingPathData {
+ destination: App
+ path: Transaction[]
+ transactions: Transaction[]
+}
+
+export interface ParamData {
+ argumentId: number
+ operationType: number
+ argumentValue: BigInt
+}
+
+export interface PermissionData {
+ allowed: boolean
+ appAddress: string
+ granteeAddress: string
+ params: ParamData[]
+ roleHash: string
+}
+
+export interface RepoData {
+ address: string
+ artifact?: string | null
+ contentUri?: string
+ lastVersion?: string
+ manifest?: string | null
+ name: string
+ registry?: string
+ registryAddress?: string
+}
+
+export interface RoleData {
+ appAddress: string
+ appId: string
+ artifact?: string | null
+ contentUri?: string
+ hash: string
+ manager?: string
+ grantees?: PermissionData[] | null
+}
+
+export interface TransactionData {
+ data: string
+ from: Address
+ to: Address
+}
+
+export interface TokenData {
+ address: Address
+ value: string | BigNumber
+ spender?: Address
+}
+
+////// METADATA //////
+
+export type Metadata = (AragonArtifact | AragonManifest)[]
-export interface AppIntent {
+export interface AppMethod {
roles: string[]
sig: string
/**
@@ -22,7 +147,7 @@ export interface AppIntent {
* The function's ABI element is included for convenience of the client
* null if ABI is not found for this signature
*/
- abi: FunctionFragment | null
+ abi: ethersUtils.FunctionFragment | null
}
// The aragon manifest requires the use of camelcase for some names
@@ -60,12 +185,12 @@ export interface AragonArtifact extends AragonAppJson {
* Includes metadata needed for radspec and transaction pathing
* initialize() function should also be included for completeness
*/
- functions: AppIntent[]
+ functions: AppMethod[]
/**
* Functions that are no longer available at `version`
*/
deprecatedFunctions: {
- [version: string]: AppIntent[]
+ [version: string]: AppMethod[]
}
/**
* The flaten source code of the contracts must be included in
diff --git a/packages/connect-core/src/utils/__test__/callscript.test.ts b/packages/connect-core/src/utils/__test__/callscript.test.ts
new file mode 100644
index 00000000..e6e9c512
--- /dev/null
+++ b/packages/connect-core/src/utils/__test__/callscript.test.ts
@@ -0,0 +1,60 @@
+import * as script from '../callScript'
+
+describe('encodeCallScript', () => {
+ const callScript = script.encodeCallScript([
+ {
+ to: '0xcafe1a77e84698c83ca8931f54a755176ef75f2c',
+ data: '0xcafe',
+ },
+ {
+ to: '0xbeefbeef03c7e5a1c29e0aa675f8e16aee0a5fad',
+ data: '0xbeef',
+ },
+ {
+ to: '0xbaaabaaa03c7e5a1c29e0aa675f8e16aee0a5fad',
+ data: '0x',
+ },
+ ])
+
+ test('callscript should start with script ID 1', () => {
+ expect(callScript.slice(0, 10)).toBe(script.CALLSCRIPT_ID)
+ })
+
+ test('first part of callscript should be address for tx 1', () => {
+ expect(callScript.slice(10, 50)).toBe(
+ 'cafe1a77e84698c83ca8931f54a755176ef75f2c'
+ )
+ })
+
+ test('second part of callscript should be data length for tx 1', () => {
+ expect(callScript.slice(50, 58)).toBe('00000002')
+ })
+
+ test('third part of callscript should be data for tx 1', () => {
+ expect(callScript.slice(58, 62)).toBe('cafe')
+ })
+
+ test('fourth part of callscript should be address for tx 2', () => {
+ expect(callScript.slice(62, 102)).toBe(
+ 'beefbeef03c7e5a1c29e0aa675f8e16aee0a5fad'
+ )
+ })
+
+ test('fifth part of callscript should be data length for tx 2', () => {
+ expect(callScript.slice(102, 110)).toBe('00000002')
+ })
+
+ test('sixth part of callscript should be data for tx 2', () => {
+ expect(callScript.slice(110, 114)).toBe('beef')
+ })
+
+ test('seventh part of callscript should be address for tx 3', () => {
+ expect(callScript.slice(114, 154)).toBe(
+ 'baaabaaa03c7e5a1c29e0aa675f8e16aee0a5fad'
+ )
+ })
+
+ test('eigth part of callscript should be data length for tx 3', () => {
+ expect(callScript.slice(154, 162)).toBe('00000000')
+ })
+})
diff --git a/packages/connect-core/src/utils/abi.ts b/packages/connect-core/src/utils/abi.ts
new file mode 100644
index 00000000..33cbfd1f
--- /dev/null
+++ b/packages/connect-core/src/utils/abi.ts
@@ -0,0 +1,36 @@
+import { utils as ethersUtils } from 'ethers'
+
+import { Abi } from '../types'
+
+export function findMethodAbiFragment(
+ abi: Abi,
+ methodSignature: string
+): ethersUtils.Fragment | undefined {
+ if (methodSignature === 'fallback') {
+ // Note that fallback functions in the ABI do not contain a `name` or `inputs` key
+ return abi.find((method) => method.type === 'fallback')
+ }
+
+ // Is the given method a full signature, e.g. 'foo(arg1,arg2,...)'
+ const fullMethodSignature =
+ Boolean(methodSignature) &&
+ methodSignature.includes('(') &&
+ methodSignature.includes(')')
+
+ const methodAbiFragment = abi
+ .filter((method) => method.type === 'function')
+ .find((method) => {
+ // If the full signature isn't given, just find the first overload declared
+ if (!fullMethodSignature) {
+ return method.name === methodSignature
+ }
+
+ const currentParameterTypes = method.inputs.map(({ type }) => type)
+ const currentMethodSignature = `${
+ method.name
+ }(${currentParameterTypes.join(',')})`
+ return currentMethodSignature === methodSignature
+ })
+
+ return methodAbiFragment
+}
diff --git a/packages/connect-core/src/utils/abis.ts b/packages/connect-core/src/utils/abis.ts
index d0c0ce01..18b540f2 100644
--- a/packages/connect-core/src/utils/abis.ts
+++ b/packages/connect-core/src/utils/abis.ts
@@ -13,3 +13,23 @@ export const forwarderAbi = [
export const forwarderFeeAbi = [
'function forwardFee() external view returns (address, uint256)',
]
+
+export const miniMeAbi = [
+ 'function balanceOf(address _owner) external view returns (uint256)',
+ 'function balanceOfAt(address _owner, uint256 _blockNumber) external view returns (uint256)',
+]
+
+export const arbitratorAbi = [
+ {
+ "inputs": [],
+ "name": "getDisputeFees",
+ "outputs": [
+ { "name": "recipient", "type": "address" },
+ { "name": "feeToken", "type": "address" },
+ { "name": "feeAmount", "type": "uint256" }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+]
diff --git a/packages/connect-core/src/utils/app.ts b/packages/connect-core/src/utils/app.ts
index cb79a80b..79e1b2ea 100644
--- a/packages/connect-core/src/utils/app.ts
+++ b/packages/connect-core/src/utils/app.ts
@@ -1,44 +1,63 @@
import { utils as ethersUtils } from 'ethers'
-import { AppIntent } from '../types'
+
+import { Abi, AppMethod } from '../types'
import { ErrorInvalid } from '../errors'
import App from '../entities/App'
-import { TransactionRequestData } from '../transactions/TransactionRequest'
export const apmAppId = (appName: string): string =>
ethersUtils.namehash(`${appName}.aragonpm.eth`)
-// Is the given method a full signature, e.g. 'foo(arg1,arg2,...)'
-export const isFullMethodSignature = (methodSignature: string): boolean => {
- return (
- Boolean(methodSignature) &&
- methodSignature.includes('(') &&
- methodSignature.includes(')')
- )
+function signatureFromAbi(signature: string, abi: Abi): string {
+ const matches = signature.match(/(.*)\((.*)\)/m)
+
+ if (!matches) {
+ throw new ErrorInvalid(`Abi has no method with signature: ${signature}`)
+ }
+
+ const name = matches[1]
+ const params = matches[2].split(',')
+
+ // If a single ABI node is found with function name and same number of parameters,
+ // generate the signature from ABI. Otherwise, use the one from artifact.
+ const functionAbis = abi
+ .filter((node) => node.name === name)
+ .filter((node) => node.inputs.length === params.length)
+
+ if (functionAbis.length === 1) {
+ return `${functionAbis[0].name}(${functionAbis[0].inputs
+ .map((input) => input.type)
+ .join(',')})`
+ }
+
+ return signature
}
-export function validateMethod(
- destination: string,
- methodSignature: string,
- destinationApp: App
-): AppIntent {
- const methods = destinationApp.intents
- if (!methods) {
- throw new ErrorInvalid(
- `No functions specified in artifact for ${destination}`
- )
+function findAppMethod(
+ app: App,
+ methodTestFn: any,
+ { allowDeprecated = true } = {}
+): AppMethod | undefined {
+ const { deprecatedFunctions, functions } = app.artifact || {}
+
+ let method
+ // First try to find the method in the current functions
+ if (Array.isArray(functions)) {
+ method = functions
+ .map((f) => {
+ return { ...f, sig: signatureFromAbi(f.sig, app.abi) }
+ })
+ .find(methodTestFn)
}
- // Find the relevant method information
- const method = methods.find((method) =>
- isFullMethodSignature(methodSignature)
- ? method.sig === methodSignature
- : // If the full signature isn't given, just select the first overload declared
- method.sig.split('(')[0] === methodSignature
- )
- if (!method) {
- throw new ErrorInvalid(
- `No method named ${methodSignature} on ${destination}`
+ if (!method && allowDeprecated) {
+ // The current functions didn't have it; try with each deprecated version's functions
+ const deprecatedFunctionsFromVersions = Object.values(
+ deprecatedFunctions || {}
)
+ if (deprecatedFunctionsFromVersions.every(Array.isArray)) {
+ // Flatten all the deprecated functions and find the method
+ method = deprecatedFunctionsFromVersions.flat().find(methodTestFn)
+ }
}
return method
@@ -50,43 +69,56 @@ export function validateMethod(
*
* @param {Object} app App artifact
* @param {Object} data Data component of a transaction to app
+ * @param {Object} options Options
+ * @param {boolean} [options.allowDeprecated] Allow deprecated functions to be returned. Defaults to true.
* @return {Object|void} Method with radspec notice and function signature, or undefined if none was found
*/
-export function findAppMethodFromIntent(
+export function findAppMethodFromData(
app: App,
- transaction: TransactionRequestData
-): AppIntent | undefined {
- const methodId = transaction.data.substring(0, 10)
-
- const checkMethodSignature = (siganture: string): boolean => {
- // Hash signature with Ethereum Identity and silce bytes
- const sigHash = ethersUtils.hexDataSlice(ethersUtils.id(siganture), 0, 4)
- return sigHash === methodId
- }
-
- const { deprecatedIntents, intents } = app || {}
+ data: string,
+ { allowDeprecated = true } = {}
+): AppMethod | undefined {
+ const methodId = data.substring(0, 10)
+ return findAppMethod(
+ app,
+ (method: AppMethod) =>
+ ethersUtils.id(method.sig).substring(0, 10) === methodId,
+ { allowDeprecated }
+ )
+}
- let method
- // First try to find the method in the current functions
- if (Array.isArray(intents)) {
- method = intents.find((method) => checkMethodSignature(method.sig))
- }
+/**
+ * Find the method descriptor corresponding to an app's method signature.
+ *
+ * @param {Object} app App
+ * @param {string} methodSignature Method signature to be called
+ * @param {Object} options Options
+ * @param {boolean} [options.allowDeprecated] Allow deprecated functions to be returned. Defaults to true.
+ * @return {Object|void} Method with radspec notice and function signature, or undefined if none was found
+ */
+export function findAppMethodFromSignature(
+ app: App,
+ methodSignature: string,
+ { allowDeprecated = true } = {}
+): AppMethod | undefined {
+ // Is the given method a full signature, e.g. 'foo(arg1,arg2,...)'
+ const fullMethodSignature =
+ Boolean(methodSignature) &&
+ methodSignature.includes('(') &&
+ methodSignature.includes(')')
- if (!method) {
- // The current functions didn't have it; try with each deprecated version's functions
- const deprecatedFunctionsFromVersions = Object.values(
- deprecatedIntents || {}
- )
- if (deprecatedFunctionsFromVersions.every(Array.isArray)) {
- // Flatten all the deprecated functions
- const allDeprecatedFunctions = ([] as AppIntent[]).concat(
- ...deprecatedFunctionsFromVersions
- )
- method = allDeprecatedFunctions.find((method) =>
- checkMethodSignature(method.sig)
- )
- }
- }
+ return findAppMethod(
+ app,
+ (method: AppMethod) => {
+ // Note that fallback functions have the signature 'fallback' in an app's artifact.json
+ if (fullMethodSignature) {
+ return method.sig === methodSignature
+ }
- return method
+ // If full signature isn't given, just match against the method names
+ const methodName = method.sig.split('(')[0]
+ return methodName === methodSignature
+ },
+ { allowDeprecated }
+ )
}
diff --git a/packages/connect-core/src/utils/callScript.ts b/packages/connect-core/src/utils/callScript.ts
index 9b396e2e..10ad51e0 100644
--- a/packages/connect-core/src/utils/callScript.ts
+++ b/packages/connect-core/src/utils/callScript.ts
@@ -1,12 +1,9 @@
import { utils as ethersUtils } from 'ethers'
import { ErrorInvalid } from '../errors'
-export const CALLSCRIPT_ID = '0x00000001'
+import { CallScriptAction } from '../types'
-interface CallScriptAction {
- to: string
- data: string
-}
+export const CALLSCRIPT_ID = '0x00000001'
interface Segment {
segment: CallScriptAction
diff --git a/packages/connect-core/src/utils/descriptions.ts b/packages/connect-core/src/utils/descriptions.ts
deleted file mode 100644
index f20ed7ae..00000000
--- a/packages/connect-core/src/utils/descriptions.ts
+++ /dev/null
@@ -1,76 +0,0 @@
-import { providers as ethersProviders } from 'ethers'
-import App from '../entities/App'
-import TransactionRequest from '../transactions/TransactionRequest'
-import { ErrorException, ErrorInvalid } from '../errors'
-import {
- decodeTransactionPath,
- TransactionWithChildren,
-} from './path/decodePath'
-import {
- tryEvaluatingRadspec,
- postprocessRadspecDescription,
-} from './radspec/index'
-
-export async function describeTransaction(
- transaction: TransactionWithChildren,
- apps: App[],
- provider?: ethersProviders.Provider
-): Promise {
- if (!transaction.to) {
- throw new ErrorInvalid(`Could not describe transaction: missing 'to'`)
- }
- if (!transaction.data) {
- throw new ErrorInvalid(`Could not describe transaction: missing 'data'`)
- }
- let description, descriptionAnnotated
- try {
- description = await tryEvaluatingRadspec(transaction, apps, provider)
-
- if (description) {
- const processed = await postprocessRadspecDescription(description, apps)
- descriptionAnnotated = processed.annotatedDescription
- description = processed.description
- }
-
- if (transaction.children) {
- // eslint-disable-next-line @typescript-eslint/no-use-before-define
- transaction.children = await describeTransactionPath(
- transaction.children,
- apps,
- provider
- )
- }
- } catch (err) {
- throw new ErrorException(`Could not describe transaction: ${err}`)
- }
-
- return new TransactionRequest({
- ...transaction,
- description,
- descriptionAnnotated,
- })
-}
-
-/**
- * Use radspec to create a human-readable description for each transaction in the given `path`
- *
- */
-export async function describeTransactionPath(
- path: TransactionWithChildren[],
- apps: App[],
- provider?: ethersProviders.Provider
-): Promise {
- return Promise.all(
- path.map((step) => describeTransaction(step, apps, provider))
- )
-}
-
-export async function describeScript(
- script: string,
- apps: App[],
- provider?: ethersProviders.Provider
-): Promise {
- const path = decodeTransactionPath(script)
-
- return describeTransactionPath(path, apps, provider)
-}
diff --git a/packages/connect-core/src/utils/path/decodePath.ts b/packages/connect-core/src/utils/descriptor/decode.ts
similarity index 59%
rename from packages/connect-core/src/utils/path/decodePath.ts
rename to packages/connect-core/src/utils/descriptor/decode.ts
index ca63b1af..7ad816ff 100644
--- a/packages/connect-core/src/utils/path/decodePath.ts
+++ b/packages/connect-core/src/utils/descriptor/decode.ts
@@ -1,20 +1,14 @@
import { ErrorInvalid } from '../../errors'
+import { StepDecoded } from '../../types'
import { isCallScript, decodeCallScript } from '../callScript'
import { isValidForwardCall, parseForwardCall } from '../forwarding'
-import { Transaction } from '../transactions'
-
-export interface TransactionWithChildren extends Transaction {
- children?: TransactionWithChildren[]
-}
/**
* Decodes an EVM callscript and returns the transaction path it describes.
*
* @return An array of Ethereum transactions that describe each step in the path
*/
-export function decodeTransactionPath(
- script: string
-): TransactionWithChildren[] {
+export function decodeForwardingPath(script: string): StepDecoded[] {
// In the future we may support more EVMScripts, but for now let's just assume we're only
// dealing with call scripts
if (!isCallScript(script)) {
@@ -23,19 +17,23 @@ export function decodeTransactionPath(
const path = decodeCallScript(script)
- return path.reduce((decodeSegments, segment) => {
- const { data } = segment
+ const decodedPath = path.map((step) => {
+ const { data } = step
let children
if (isValidForwardCall(data)) {
const forwardedEvmScript = parseForwardCall(data)
try {
- children = decodeTransactionPath(forwardedEvmScript)
- // eslint-disable-next-line no-empty
+ children = decodeForwardingPath(forwardedEvmScript)
} catch (err) {}
}
- return decodeSegments.concat({ ...segment, children })
- }, [] as TransactionWithChildren[])
+ return {
+ ...step,
+ children,
+ }
+ })
+
+ return decodedPath
}
diff --git a/packages/connect-core/src/utils/descriptor/describe.ts b/packages/connect-core/src/utils/descriptor/describe.ts
new file mode 100644
index 00000000..f5cd6559
--- /dev/null
+++ b/packages/connect-core/src/utils/descriptor/describe.ts
@@ -0,0 +1,106 @@
+import { providers as ethersProviders } from 'ethers'
+
+import {
+ tryEvaluatingRadspec,
+ tryDescribingUpdateAppIntent,
+ postprocessRadspecDescription,
+} from '../radspec/index'
+import { StepDecoded, StepDescribed, PostProcessDescription } from '../../types'
+import App from '../../entities/App'
+import Transaction from '../../entities/Transaction'
+
+export async function describeStep(
+ step: StepDecoded,
+ installedApps: App[],
+ provider: ethersProviders.Provider
+): Promise {
+ let decoratedStep
+ // TODO: Add intent Basket support
+
+ // Single transaction step
+ // First see if the step can be handled with a specialized descriptor
+ try {
+ decoratedStep = await tryDescribingUpdateAppIntent(step, installedApps)
+ } catch (err) {}
+
+ // Finally, if the step wasn't handled yet, evaluate via radspec normally
+ if (!decoratedStep) {
+ try {
+ decoratedStep = await tryEvaluatingRadspec(step, installedApps, provider)
+ } catch (err) {}
+ }
+
+ // Annotate the description, if one was found
+ if (decoratedStep?.description) {
+ try {
+ const {
+ description,
+ annotatedDescription,
+ } = await postprocessRadspecDescription(
+ decoratedStep.description,
+ installedApps
+ )
+ decoratedStep.description = description
+ decoratedStep.annotatedDescription = annotatedDescription ?? []
+ } catch (err) {}
+ }
+
+ if (decoratedStep?.children) {
+ decoratedStep.children = await describePath(
+ decoratedStep.children,
+ installedApps,
+ provider
+ )
+ }
+
+ return decoratedStep || { ...step, description: '' }
+}
+
+/**
+ * Use radspec to create a human-readable description for each step in the given `path`
+ *
+ */
+export async function describePath(
+ path: StepDecoded[],
+ installedApps: App[],
+ provider: ethersProviders.Provider
+): Promise {
+ return Promise.all(
+ path.map(async (step) => describeStep(step, installedApps, provider))
+ )
+}
+
+export async function describeTransaction(
+ transaction: Transaction,
+ installedApps: App[],
+ provider: ethersProviders.Provider
+): Promise {
+ if (!transaction.to) {
+ throw new Error(`Could not describe transaction: missing 'to'`)
+ }
+ if (!transaction.data) {
+ throw new Error(`Could not describe transaction: missing 'data'`)
+ }
+
+ let description
+ try {
+ description = await tryEvaluatingRadspec(
+ transaction,
+ installedApps,
+ provider
+ )
+
+ if (description) {
+ return postprocessRadspecDescription(
+ description.description,
+ installedApps
+ )
+ }
+ } catch (err) {
+ throw new Error(`Could not describe transaction: ${err}`)
+ }
+
+ return {
+ description,
+ }
+}
diff --git a/packages/connect-core/src/utils/descriptor/index.ts b/packages/connect-core/src/utils/descriptor/index.ts
new file mode 100644
index 00000000..621237e3
--- /dev/null
+++ b/packages/connect-core/src/utils/descriptor/index.ts
@@ -0,0 +1,45 @@
+import { AppOrAddress, StepDescribed } from '../../types'
+import App from '../../entities/App'
+
+type ForwardingPathDescriptionTreeEntry =
+ | AppOrAddress
+ | [AppOrAddress, ForwardingPathDescriptionTreeEntry[]]
+
+type ForwardingPathDescriptionTree = ForwardingPathDescriptionTreeEntry[]
+
+export default class ForwardingPathDescription {
+ #installedApps: App[]
+ readonly describedSteps: StepDescribed[]
+
+ constructor(describedSteps: StepDescribed[], installedApps: App[]) {
+ this.#installedApps = installedApps
+ this.describedSteps = describedSteps
+ }
+
+ // Return a tree that can get used to render the path.
+ tree(): ForwardingPathDescriptionTree {
+ const docoratedStep = this.describedSteps.map(async (step) => {
+ const app = this.#installedApps.find((app) => app.address === step.to)
+
+ if (app) {
+ return {
+ ...step,
+ app,
+ }
+ }
+ })
+ return []
+ }
+
+ // Renders the forwarding path description as text
+ toString(): string {
+ return this.tree().toString()
+ }
+
+ // TBD: a utility that makes it easy to render the tree,
+ // e.g. as a nested list in HTML or React.
+ reduce(callback: Function): any {}
+}
+
+export { describePath, describeTransaction } from './describe'
+export { decodeForwardingPath } from './decode'
diff --git a/packages/connect-core/src/utils/index.ts b/packages/connect-core/src/utils/index.ts
index 87b55ee2..9e99dde4 100644
--- a/packages/connect-core/src/utils/index.ts
+++ b/packages/connect-core/src/utils/index.ts
@@ -1,11 +1,12 @@
export * from './address'
export * from './app-connectors'
+export * from './descriptor/index'
export * from './cache-store'
-export * from './descriptions'
export * from './ipfs'
export * from './misc'
export * from './network'
export * from './subscriptions'
export { decodeCallScript, encodeCallScript } from './callScript'
export { fetchMetadata } from './metadata'
-export { findAppMethodFromIntent } from './app'
+export * from './app'
+export * from './abis'
diff --git a/packages/connect-core/src/utils/intent.ts b/packages/connect-core/src/utils/intent.ts
new file mode 100644
index 00000000..c68a8c97
--- /dev/null
+++ b/packages/connect-core/src/utils/intent.ts
@@ -0,0 +1,84 @@
+import { Address } from '@aragon/connect-types'
+import { utils as ethersUtils, providers as ethersProvider } from 'ethers'
+
+import { addressesEqual } from './address'
+import {
+ decodeKernelSetAppParameters,
+ isKernelAppCodeNamespace,
+ isKernelSetAppIntent,
+} from './kernel'
+import { getForwardingPath, getACLForwardingPath } from './path/index'
+import { StepDecoded } from '../types'
+import App from '../entities/App'
+import ForwardingPath from '../entities/ForwardingPath'
+
+export async function appIntent(
+ sender: Address,
+ destinationApp: App,
+ methodSignature: string,
+ params: any[],
+ installedApps: App[],
+ provider: ethersProvider.Provider
+): Promise {
+ const acl = installedApps.find((app) => app.name === 'acl')
+
+ if (acl && addressesEqual(destinationApp.address, acl.address)) {
+ try {
+ return getACLForwardingPath(
+ sender,
+ acl,
+ methodSignature,
+ params,
+ installedApps,
+ provider
+ )
+ } catch (_) {
+ // emtpy path
+ return new ForwardingPath(
+ {
+ destination: destinationApp,
+ path: [],
+ transactions: [],
+ },
+ installedApps,
+ provider
+ )
+ }
+ }
+
+ return getForwardingPath(
+ sender,
+ destinationApp,
+ methodSignature,
+ params,
+ installedApps,
+ provider
+ )
+}
+
+export function filterAndDecodeAppUpgradeIntents(
+ intents: StepDecoded[],
+ installedApps: App[]
+): ethersUtils.Result[] {
+ const kernel = installedApps.find((app) => app.name === 'kernel')
+
+ if (!kernel) {
+ throw new Error(`Organization not found.`)
+ }
+
+ return (
+ intents
+ // Filter for setApp() calls to the kernel
+ .filter((intent) => isKernelSetAppIntent(kernel, intent))
+ // Try to decode setApp() params
+ .map((intent) => {
+ try {
+ return decodeKernelSetAppParameters(intent.data)
+ } catch (_) {}
+
+ return []
+ })
+ // Filter for changes to APP_BASES_NAMESPACE
+ .filter((result) => isKernelAppCodeNamespace(result['namesapce']))
+ )
+}
diff --git a/packages/connect-core/src/utils/kernel.ts b/packages/connect-core/src/utils/kernel.ts
index 2007f2a6..d8153bdd 100644
--- a/packages/connect-core/src/utils/kernel.ts
+++ b/packages/connect-core/src/utils/kernel.ts
@@ -1,5 +1,10 @@
import { utils as ethersUtils } from 'ethers'
+import { addressesEqual } from './address'
+import { findAppMethodFromData } from './app'
+import { StepDecoded } from '../types'
+import App from '../entities/App'
+
const CORE_NAMESPACE = ethersUtils.solidityKeccak256(['string'], ['core'])
const APP_ADDR_NAMESPACE = ethersUtils.solidityKeccak256(['string'], ['app'])
const APP_BASES_NAMESPACE = ethersUtils.solidityKeccak256(['string'], ['base'])
@@ -10,6 +15,12 @@ const KERNEL_NAMESPACES_NAMES = new Map([
[APP_BASES_NAMESPACE, 'App code'],
])
+const SET_APP_ABI = [
+ { name: 'namespace', type: 'bytes32' },
+ { name: 'appId', type: 'bytes32' },
+ { name: 'appAddress', type: 'address' },
+].map((param) => ethersUtils.ParamType.from(param))
+
interface KernelNamespace {
name: string
hash: string
@@ -20,3 +31,36 @@ export function getKernelNamespace(hash: string): KernelNamespace | null {
? { name: KERNEL_NAMESPACES_NAMES.get(hash) as string, hash }
: null
}
+
+/**
+ * Decode `Kernel.setApp()` parameters based on transaction data.
+ *
+ * @param {Object} data Transaction data
+ * @return {Object} Decoded parameters for `setApp()` (namespace, appId, appAddress)
+ */
+export function decodeKernelSetAppParameters(data: string): ethersUtils.Result {
+ // Strip 0x prefix + bytes4 sig to get parameter data
+ const paramData = data.substring(10)
+ return ethersUtils.defaultAbiCoder.decode(SET_APP_ABI, paramData)
+}
+
+export function isKernelAppCodeNamespace(namespaceHash: string): boolean {
+ return namespaceHash === APP_BASES_NAMESPACE
+}
+
+/**
+ * Is the transaction intent for `Kernel.setApp()`?
+ *
+ * @param {Object} kernelApp App artifact for Kernel
+ * @param {Object} intent Transaction intent
+ * @return {Boolean} Whether the intent is `Kernel.setApp()`
+ */
+export function isKernelSetAppIntent(
+ kernelApp: App,
+ intent: StepDecoded
+): boolean {
+ if (!addressesEqual(kernelApp.address, intent.to)) return false
+
+ const method = findAppMethodFromData(kernelApp, intent.data)
+ return !!method && method.sig === 'setApp(bytes32,bytes32,address)'
+}
diff --git a/packages/connect-core/src/utils/metadata.ts b/packages/connect-core/src/utils/metadata.ts
index 3d964fdd..38b3da61 100644
--- a/packages/connect-core/src/utils/metadata.ts
+++ b/packages/connect-core/src/utils/metadata.ts
@@ -1,6 +1,5 @@
-import fetch from 'isomorphic-unfetch'
import { AragonArtifact, AragonManifest, IpfsResolver } from '../types'
-import { ErrorConnection, ErrorInvalid, ErrorUnexpectedResult } from '../errors'
+import { ErrorInvalid } from '../errors'
import {
getApmInternalAppInfo,
getAragonOsInternalAppInfo,
diff --git a/packages/connect-core/src/utils/path/calculatePath.ts b/packages/connect-core/src/utils/path/calculatePath.ts
index 25d4b0a5..37872d11 100644
--- a/packages/connect-core/src/utils/path/calculatePath.ts
+++ b/packages/connect-core/src/utils/path/calculatePath.ts
@@ -1,56 +1,19 @@
-import { providers as ethersProviders } from 'ethers'
-import { AppIntent } from '../../types'
+import { providers as ethersProviders, utils as ethersUtils } from 'ethers'
+
import { ErrorInvalid } from '../../errors'
+import { TransactionPath } from '../../types'
import App from '../../entities/App'
-import {
- ANY_ENTITY,
- addressesEqual,
- includesAddress,
- isAddress,
-} from '../address'
-import { isFullMethodSignature } from '../app'
+import Transaction from '../../entities/Transaction'
+import { ANY_ENTITY, addressesEqual, includesAddress } from '../address'
+import { findAppMethodFromSignature } from '../app'
import { encodeCallScript } from '../callScript'
import { canForward } from '../forwarding'
import {
- Transaction,
createDirectTransactionForApp,
createForwarderTransactionBuilder,
- buildForwardingFeePretransaction,
+ buildForwardingFeePreTransactions,
} from '../transactions'
-interface PathData {
- forwardingFeePretransaction?: Transaction
- path: Transaction[]
-}
-
-function validateMethod(
- destination: string,
- methodSignature: string,
- destinationApp: App
-): AppIntent {
- const methods = destinationApp.intents
- if (!methods) {
- throw new ErrorInvalid(
- `No functions specified in artifact for ${destination}`
- )
- }
-
- // Find the relevant method information
- const method = methods.find((method) =>
- isFullMethodSignature(methodSignature)
- ? method.sig === methodSignature
- : // If the full signature isn't given, just select the first overload declared
- method.sig.split('(')[0] === methodSignature
- )
- if (!method) {
- throw new ErrorInvalid(
- `No method named ${methodSignature} on ${destination}`
- )
- }
-
- return method
-}
-
/**
* Calculate the forwarding path for a transaction to `destination`
* that invokes `directTransaction`.
@@ -62,10 +25,10 @@ async function calculateForwardingPath(
forwardersWithPermission: string[],
forwarders: string[],
provider: ethersProviders.Provider
-): Promise {
+): Promise {
// No forwarders can perform the requested action
if (forwardersWithPermission.length === 0) {
- return { path: [] }
+ return { path: [], transactions: [] }
}
const createForwarderTransaction = createForwarderTransactionBuilder(
@@ -73,26 +36,43 @@ async function calculateForwardingPath(
directTransaction
)
+ const buildForwardingPath = async (
+ forwarder: string,
+ script: string,
+ path: Transaction[],
+ provider: ethersProviders.Provider
+ ): Promise => {
+ const transaction = createForwarderTransaction(forwarder, script)
+
+ // Only apply pretransactions to the first transaction in the path
+ // as it's the only one that will be executed by the user
+ try {
+ const forwardingFeePreTransactions = await buildForwardingFeePreTransactions(
+ transaction,
+ provider
+ )
+ // If that happens, we give up as we should've been able to perform the action with this
+ // forwarding path
+ return {
+ transactions: [...forwardingFeePreTransactions, transaction],
+ path: [transaction, ...path],
+ }
+ } catch (err) {
+ return { path: [], transactions: [] }
+ }
+ }
+
// Check if one of the forwarders that has permission to perform an action
// with `sig` on `address` can forward for us directly
for (const forwarder of forwardersWithPermission) {
const script = encodeCallScript([directTransaction])
if (await canForward(forwarder, sender, script, provider)) {
- const transaction = createForwarderTransaction(forwarder, script)
- try {
- const forwardingFeePretransaction = await buildForwardingFeePretransaction(
- transaction,
- provider
- )
- // If that happens, we give up as we should've been able to perform the action with this
- // forwarder
- return {
- forwardingFeePretransaction,
- path: [transaction, directTransaction],
- }
- } catch (err) {
- return { path: [] }
- }
+ return buildForwardingPath(
+ forwarder,
+ script,
+ [directTransaction],
+ provider
+ )
}
}
@@ -143,24 +123,7 @@ async function calculateForwardingPath(
if (await canForward(forwarder, sender, script, provider)) {
// The previous forwarder can forward a transaction for this forwarder,
// and this forwarder can forward for our address, so we have found a path
- const transaction = createForwarderTransaction(forwarder, script)
-
- // Only apply pretransactions to the first transaction in the path
- // as it's the only one that will be executed by the user
- try {
- const forwardingFeePretransaction = await buildForwardingFeePretransaction(
- transaction,
- provider
- )
- // If that happens, we give up as we should've been able to perform the action with this
- // forwarding path
- return {
- forwardingFeePretransaction,
- path: [transaction, ...path],
- }
- } catch (err) {
- return { path: [] }
- }
+ return buildForwardingPath(forwarder, script, path, provider)
} else {
// The previous forwarder can forward a transaction for this forwarder,
// but this forwarder can not forward for our address, so we add it as a
@@ -180,7 +143,7 @@ async function calculateForwardingPath(
queue.push([path, nextQueue])
} while (queue.length)
- return { path: [] }
+ return { path: [], transactions: [] }
}
/**
@@ -190,47 +153,44 @@ async function calculateForwardingPath(
*/
export async function calculateTransactionPath(
sender: string,
- destination: string,
+ destinationApp: App,
methodSignature: string,
params: any[],
apps: App[],
provider: ethersProviders.Provider,
finalForwarder?: string //Address of the final forwarder that can perfom the action. Needed for actions that aren't in the ACL but whose execution depends on other factors
-): Promise {
- // Get the destination app
- const destinationApp = apps.find((app) => app.address == destination)
- if (!destinationApp) {
- throw new ErrorInvalid(
- `Transaction path destination (${destination}) is not an installed app`
- )
- }
-
- // Make sure the method signature is correct
- const method = validateMethod(destination, methodSignature, destinationApp)
-
- const finalForwarderProvided = finalForwarder
- ? isAddress(finalForwarder)
- : false
+): Promise {
+ // The direct transaction we eventually want to perform
const directTransaction = await createDirectTransactionForApp(
sender,
destinationApp,
- method.sig,
+ methodSignature,
params
)
+ const finalForwarderProvided = finalForwarder
+ ? ethersUtils.isAddress(finalForwarder)
+ : false
+
+ const method = findAppMethodFromSignature(destinationApp, methodSignature)
+ if (!method) {
+ throw new ErrorInvalid(
+ `No method named ${methodSignature} on ${destinationApp.address}`
+ )
+ }
// We can already assume the user is able to directly invoke the action if:
// - The method has no ACL requirements and no final forwarder was given, or
// - The final forwarder matches the sender
if (
- (method.roles.length === 0 && !finalForwarderProvided) ||
+ (method?.roles.length === 0 && !finalForwarderProvided) ||
(finalForwarder && addressesEqual(finalForwarder, sender))
) {
try {
- return { path: [directTransaction] }
+ return { path: [directTransaction], transactions: [directTransaction] }
} catch (_) {
// If the direct transaction fails, we give up as we should have been able to
// perform the action directly
- return { path: [] }
+ return { path: [], transactions: [] }
}
}
@@ -245,7 +205,7 @@ export async function calculateTransactionPath(
if (!includesAddress(forwarders, finalForwarder)) {
// Final forwarder was given, but did not match any available forwarders, so no path
// could be found
- return { path: [] }
+ return { path: [], transactions: [] }
}
// Only attempt to find path with declared final forwarder; assume the final forwarder
@@ -257,12 +217,15 @@ export async function calculateTransactionPath(
const role = (await destinationApp.roles()).find(
(role) => role.name === method.roles[0]
)
+
const allowedEntities =
- role?.permissions?.map((permission) => permission.granteeAddress) || []
+ role?.permissions
+ ?.filter((permission) => permission.allowed)
+ .map((permission) => permission.granteeAddress) || []
// No one has access, so of course we don't as well
if (allowedEntities.length === 0) {
- return { path: [] }
+ return { path: [], transactions: [] }
}
// User may have permission; attempt direct transaction
@@ -271,7 +234,7 @@ export async function calculateTransactionPath(
includesAddress(allowedEntities, ANY_ENTITY)
) {
try {
- return { path: [directTransaction] }
+ return { path: [directTransaction], transactions: [directTransaction] }
} catch (_) {
// Don't immediately fail as the permission could have parameters applied that
// disallows the user from the current action and forces us to use the full
diff --git a/packages/connect-core/src/utils/path/getACLForwardingPath.ts b/packages/connect-core/src/utils/path/getACLForwardingPath.ts
new file mode 100644
index 00000000..8d183a09
--- /dev/null
+++ b/packages/connect-core/src/utils/path/getACLForwardingPath.ts
@@ -0,0 +1,95 @@
+import { Address } from '@aragon/connect-types'
+import { providers as ethersProviders } from 'ethers'
+
+import { getForwardingPath } from './getForwardingPath'
+import { findMethodAbiFragment } from '../abi'
+import { findAppMethodFromSignature } from '../app'
+import App from '../../entities/App'
+import ForwardingPath from '../../entities/ForwardingPath'
+
+/**
+ * Get the permission manager for an `app`'s and `role`.
+ *
+ * @param {string} appAddress
+ * @param {string} roleHash
+ * @return {Promise} The permission manager
+ */
+async function getPermissionManager(
+ appAddress: Address,
+ roleHash: string,
+ installedApps: App[]
+) {
+ const app = installedApps.find((app) => app.address === appAddress)
+ const roles = await app?.roles()
+
+ return roles?.find((role) => role.hash === roleHash)?.manager
+}
+
+/**
+ * Calculates transaction path for performing a method on the ACL
+ *
+ * @param {string} methodSignature
+ * @param {Array<*>} params
+ * @return {Promise>} An array of Ethereum transactions that describe each step in the path
+ */
+export async function getACLForwardingPath(
+ sender: Address,
+ acl: App,
+ methodSignature: string,
+ params: any[],
+ installedApps: App[],
+ provider: ethersProviders.Provider
+): Promise {
+ const method = findAppMethodFromSignature(acl, methodSignature, {
+ allowDeprecated: false,
+ })
+ if (!method) {
+ throw new Error(`No method named ${methodSignature} on ACL`)
+ }
+
+ if (method.roles && method.roles.length !== 0) {
+ // This action can be done with regular transaction pathing (it's protected by an ACL role)
+ return getForwardingPath(
+ sender,
+ acl,
+ methodSignature,
+ params,
+ installedApps,
+ provider
+ )
+ } else {
+ // Some ACL functions don't have a role and are instead protected by a manager
+ // Inspect the matched method's ABI to find the position of the 'app' and 'role' parameters
+ // needed to get the permission manager
+ const methodAbiFragment = findMethodAbiFragment(acl.abi, methodSignature)
+ if (!methodAbiFragment) {
+ throw new Error(`Method ${method} not found on ACL ABI`)
+ }
+
+ const inputNames = methodAbiFragment.inputs.map((input) => input.name)
+ const appIndex = inputNames.indexOf('_app')
+ const roleIndex = inputNames.indexOf('_role')
+
+ if (appIndex === -1 || roleIndex === -1) {
+ throw new Error(
+ `Method ${methodSignature} doesn't take _app and _role as input. Permission manager cannot be found.`
+ )
+ }
+
+ const manager = await getPermissionManager(
+ params[appIndex],
+ params[roleIndex],
+ installedApps
+ )
+
+ return getForwardingPath(
+ sender,
+ acl,
+ methodSignature,
+ params,
+ installedApps,
+ provider,
+ manager
+ )
+ }
+}
diff --git a/packages/connect-core/src/utils/path/getForwardingPath.ts b/packages/connect-core/src/utils/path/getForwardingPath.ts
new file mode 100644
index 00000000..9d8b7edf
--- /dev/null
+++ b/packages/connect-core/src/utils/path/getForwardingPath.ts
@@ -0,0 +1,46 @@
+import { Address } from '@aragon/connect-types'
+import { providers as ethersProviders } from 'ethers'
+
+import { calculateTransactionPath } from './calculatePath'
+import App from '../../entities/App'
+import ForwardingPath from '../../entities/ForwardingPath'
+
+/**
+ * Calculate the transaction path for a transaction to `destination`
+ * that invokes `methodSignature` with `params`.
+ *
+ * @param {string} destination
+ * @param {string} methodSignature
+ * @param {Array<*>} params
+ * @param {string} [finalForwarder] Address of the final forwarder that can perfom the action
+ * @return {Promise>} An array of Ethereum transactions that describe each step in the path
+ */
+export async function getForwardingPath(
+ sender: Address,
+ destinationApp: App,
+ methodSignature: string,
+ params: any[],
+ installedApps: App[],
+ provider: ethersProviders.Provider,
+ finalForwarder?: Address
+): Promise {
+ const { path, transactions } = await calculateTransactionPath(
+ sender,
+ destinationApp,
+ methodSignature,
+ params,
+ installedApps,
+ provider,
+ finalForwarder
+ )
+
+ return new ForwardingPath(
+ {
+ destination: destinationApp,
+ path,
+ transactions,
+ },
+ installedApps,
+ provider
+ )
+}
diff --git a/packages/connect-core/src/utils/path/index.ts b/packages/connect-core/src/utils/path/index.ts
new file mode 100644
index 00000000..8da671b9
--- /dev/null
+++ b/packages/connect-core/src/utils/path/index.ts
@@ -0,0 +1,3 @@
+export { calculateTransactionPath } from './calculatePath'
+export { getForwardingPath } from './getForwardingPath'
+export { getACLForwardingPath } from './getACLForwardingPath'
diff --git a/packages/connect-core/src/utils/radspec/index.ts b/packages/connect-core/src/utils/radspec/index.ts
index f98323e5..7b4fef46 100644
--- a/packages/connect-core/src/utils/radspec/index.ts
+++ b/packages/connect-core/src/utils/radspec/index.ts
@@ -1,13 +1,14 @@
-import { providers as ethersProviders } from 'ethers'
import * as radspec from 'radspec'
+import { providers as ethersProviders } from 'ethers'
+
import { addressesEqual } from '../address'
-import { findAppMethodFromIntent } from '../app'
+import { findAppMethodFromData } from '../app'
+import { filterAndDecodeAppUpgradeIntents } from '../intent'
+import { Abi, AppMethod, StepDecoded, StepDescribed } from '../../types'
import App from '../../entities/App'
-import { TransactionRequestData } from '../../transactions/TransactionRequest'
-import { Abi, AppIntent } from '../../types'
interface FoundMethod {
- method?: AppIntent
+ method?: AppMethod
abi?: Abi
}
@@ -15,22 +16,24 @@ interface FoundMethod {
* Attempt to describe intent via radspec.
*/
export async function tryEvaluatingRadspec(
- intent: TransactionRequestData,
- apps: App[],
- provider?: ethersProviders.Provider // Decorated intent with description, if one could be made
-): Promise {
- const app = apps.find((app) => addressesEqual(app.address, intent.to))
+ intent: StepDecoded,
+ installedApps: App[],
+ provider: ethersProviders.Provider // Decorated intent with description, if one could be made
+): Promise {
+ const app = installedApps.find((app) =>
+ addressesEqual(app.address, intent.to)
+ )
// If the intent matches an installed app, use only that app to search for a
// method match, otherwise fallback to searching all installed apps
- const appsToSearch = app ? [app] : apps
+ const appsToSearch = app ? [app] : installedApps
const foundMethod = appsToSearch.reduce(
(found, app) => {
if (found) {
return found
}
- const method = findAppMethodFromIntent(app, intent)
+ const method = findAppMethodFromData(app, intent.data)
if (method) {
return {
method,
@@ -65,10 +68,37 @@ export async function tryEvaluatingRadspec(
}
}
- return evaluatedNotice
+ return { ...intent, description: evaluatedNotice }
+}
+
+/**
+ * Attempt to describe a setApp() intent. Only describes the APP_BASE namespace.
+ *
+ * @param {Object} intent transaction intent
+ * @param {Object} wrapper
+ * @return {Promise