-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 6468687
Showing
12 changed files
with
385 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 1,6 @@ | ||
/node_modules | ||
yarn.lock | ||
chains.txt | ||
deb.js | ||
main.js | ||
main2.js |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 1,56 @@ | ||
# DAI <> USD P2P swap protocol | ||
|
||
|
||
**Problem:** P2P crypto swaps require bank transfer confirmation. This leads to centralization, delays and errors. | ||
|
||
*example: swap DAI to USD on Bank of America* | ||
|
||
**Solution:** Protocol for P2P swaps based on onchain wire transfer confirmation | ||
|
||
## How it works | ||
1. Bob creates an deal to sell DAI | ||
2. Alice accepts the deal and froze Bob's DAI | ||
3. Alice sends USD via Zelle | ||
4. Alice creates onchain proof of her Zelle transfer | ||
5. Alice using this confirmation closes the deal and takes Bob's DAI | ||
|
||
|
||
## Onchain proof for Bank (Zelle) transfer | ||
|
||
The confirmation works with Plaid and the LIT Protocol nodes | ||
|
||
1. Alice connects Plaid and creates an API key with access to transaction history from the bank | ||
2. Alice calls Programmable PKPs action on LIT Protocol with Plaid transactionId and API key ([demo.js](lit-action/demo.js)) | ||
3. Inside the `LitAction` executes the request on Plaid and verifies that the transaction actually exists in Alice's bank account. ([litActionPlaidCheck.js](lit-action/litActionPlaidCheck.js)) | ||
4. The `LitAction` signs the transaction information using the decentralized `signEcdsa()` | ||
5. Adisa adds signed transaction to the "vault" smart contract ([plaid-transactions-storage.sol](contracts/plaid-transactions-storage.sol)) and now everyone can verify that she actually made the transaction from her bank account. | ||
|
||
![Cover.png](res/Cover.png) | ||
|
||
## Step-by-step-swap | ||
1. Bob creates an deal to sell DAI on [p2p-market](contracts/p2p-market.sol) (`createPaymentRequest(...)`) | ||
2. Alice accepts the deal and froze Bob's DAI (`confirmPayment(...)`) | ||
3. Alice sends USD via Zelle | ||
4. Alice creates onchain proof of her Zelle transfer on [plaid-transactions-storage](contracts/plaid-transactions-storage.sol) (`addTransaction(...)`) | ||
5. Alice using this confirmation closes the deal and takes Bob's DAI on [p2p-market](contracts/p2p-market.sol) (`reservePayment(...)`) | ||
|
||
|
||
## Main components: | ||
|
||
- [p2p-market.sol](contracts/p2p-market.sol) - P2P Market smart contract | ||
- [plaid-transactions-storage.sol](contracts/plaid-transactions-storage.sol) - onchain wire transactions storage | ||
- [litActionPlaidCheck.js](lit-action/litActionPlaidCheck.js) - LIT Action to verify Plaid requests | ||
- [bos](bos) - Frontend | ||
|
||
# Demo: | ||
|
||
|
||
|
||
Url: https://bos.gg/#/azbang.near/widget/peer2peer | ||
|
||
# Source | ||
- BOS Component: https://bos.gg/#/azbang.near/widget/peer2peer | ||
- LIT-Protocol action ipfsId: `QmVcetXaAnDcHmpX6cWxsGKGSoid4buVK2PkXQEDmgh6wQ` | ||
- LIT-Protocol PKPs: https://explorer.litprotocol.com/pkps/110216617645918104171481682055149393821345448971586480839222858944196209976493 | ||
- Goerli Plaid transactions endpoint: https://goerli.etherscan.io/address/0x00e42dc2248f37a28d39f790d49230e2ddd37b99 | ||
- Goerli P2P Market: https://goerli.etherscan.io/address/0xa3724030aA74016d1AE1e1B54bD79608F0E5866F |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 1,133 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
interface IERC20 { | ||
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); | ||
function transfer(address recipient, uint256 amount) external returns (bool); | ||
} | ||
|
||
interface ITransactionChecker { | ||
struct Transaction { | ||
int256 amount; | ||
string name; | ||
int256 timestamp; | ||
address sender; | ||
} | ||
|
||
function getTransaction(bytes32 transactionId) external view returns (Transaction memory); | ||
} | ||
|
||
contract P2PPayments { | ||
struct PaymentRequest { | ||
address payable requester; | ||
uint256 amount; | ||
bytes32 zelleEmailHash; | ||
address payable executor; | ||
} | ||
|
||
IERC20 public usdtToken; | ||
ITransactionChecker public checkerContract; | ||
|
||
mapping(uint256 => PaymentRequest) public requests; | ||
uint256 public nextRequestId = 1; | ||
uint256 public minEthReserveAmount = 0.001 ether; | ||
address owner; | ||
|
||
// Events to log payment-related actions | ||
event PaymentRequested(uint256 requestId, address indexed requester, uint256 amount, bytes32 zelleEmailHash); | ||
event PaymentReserved(uint256 requestId, address indexed reserver); | ||
event PaymentConfirmed(uint256 requestId, address indexed confirmer); | ||
|
||
// Constructor to initialize the contract with addresses of ERC20 token and TransactionChecker | ||
constructor(address _usdtToken, address _checkerContract) { | ||
usdtToken = IERC20(_usdtToken); | ||
checkerContract = ITransactionChecker(_checkerContract); | ||
owner = msg.sender; | ||
} | ||
|
||
// Modifier to allow only the requester to perform certain actions | ||
modifier onlyRequester(uint256 requestId) { | ||
require(msg.sender == requests[requestId].requester, "Only the requester can perform this action"); | ||
_; | ||
} | ||
|
||
// Modifier to ensure that a payment has not been reserved | ||
modifier paymentNotReserved(uint256 requestId) { | ||
require(requests[requestId].executor == address(0), "Payment already reserved"); | ||
_; | ||
} | ||
|
||
// Function to create a new payment request | ||
function createPaymentRequest(uint256 amount, bytes32 _zelleEmailHash) external { | ||
require(amount == 10 || amount == 100, "Invalid amount"); | ||
|
||
requests[nextRequestId] = PaymentRequest({ | ||
requester: payable(msg.sender), | ||
amount: amount, | ||
zelleEmailHash: _zelleEmailHash, | ||
executor: payable(address(0)) | ||
}); | ||
|
||
emit PaymentRequested(nextRequestId, msg.sender, amount, _zelleEmailHash); | ||
nextRequestId ; | ||
} | ||
|
||
// reserve a payment so avoid parallel execution | ||
function reservePayment(uint256 requestId) external payable paymentNotReserved(requestId) { | ||
require(msg.value >= minEthReserveAmount, "Transaction must include at least 0.001 ETH"); | ||
|
||
PaymentRequest storage request = requests[requestId]; | ||
|
||
usdtToken.transferFrom(request.requester, address(this), request.amount); | ||
request.executor = payable(msg.sender); | ||
|
||
emit PaymentReserved(requestId, msg.sender); | ||
} | ||
|
||
// Modifier to allow only the owner of the contract to call certain functions | ||
modifier onlyOwner() { | ||
require(msg.sender == owner, "Only the owner can call this function"); | ||
_; | ||
} | ||
|
||
// set the minimum ETH fee amount | ||
function set_minEthReserveAmount(uint256 _minEthReserveAmount) public onlyOwner { | ||
minEthReserveAmount = _minEthReserveAmount; | ||
} | ||
|
||
// withdraw ETH from the contract (only owner) | ||
function withdrawEther() public onlyOwner { | ||
payable(owner).transfer(address(this).balance); | ||
} | ||
|
||
// confirm a payment | ||
function confirmPayment(uint256 requestId, bytes32 transactionId) external onlyRequester(requestId) { | ||
PaymentRequest storage request = requests[requestId]; | ||
|
||
require(request.executor == msg.sender, "Only executor can close request"); | ||
|
||
// check proof from LIT protocol gateaway | ||
ITransactionChecker.Transaction memory transaction = checkerContract.getTransaction(transactionId); | ||
require(keccak256(abi.encodePacked(transaction.name)) == request.zelleEmailHash, "Invalid transaction name hash"); | ||
|
||
// complete transfer | ||
usdtToken.transfer(request.executor, request.amount); | ||
|
||
delete requests[requestId]; | ||
emit PaymentConfirmed(requestId, msg.sender); | ||
} | ||
|
||
// Function to get payment requests by their IDs | ||
function getPaymentRequestsByIds(uint256[] memory requestIds) external view returns (PaymentRequest[] memory) { | ||
uint256 count = requestIds.length; | ||
PaymentRequest[] memory result = new PaymentRequest[](count); | ||
|
||
for (uint256 i = 0; i < count; i ) { | ||
uint256 requestId = requestIds[i]; | ||
require(requestId > 0 && requestId < nextRequestId, "Invalid request ID"); | ||
result[i] = requests[requestId]; | ||
} | ||
|
||
return result; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 1,44 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
contract TransactionStorage { | ||
struct Transaction { | ||
int256 amount; | ||
string name; | ||
int256 timestamp; | ||
address sender; | ||
} | ||
|
||
event NewTransaction(bytes32 transaction_id, int256 amount); | ||
address constant SIGNER_ADDRESS = 0xe156276E5d9c5920661F90C3c35906adefD6C437; | ||
mapping(bytes32 => Transaction) private transactions; | ||
|
||
// Function to add a new transaction to the storage | ||
function addTransaction(bytes memory serializedData, bytes32 r, bytes32 s, uint8 v) public { | ||
// Calculate the hash of the serialized data | ||
bytes32 hash = keccak256(serializedData); | ||
|
||
// Recover the address of the signer using the provided signature | ||
address signer = ecrecover(hash, v, r, s); | ||
|
||
// Ensure that the signer is the expected signer from LIT Protocol | ||
require(signer == SIGNER_ADDRESS, "Invalid signature"); | ||
|
||
// Decode the serialized data to extract transaction details | ||
(bytes32 transaction_id, int256 amount, string memory name, int256 timestamp) = abi.decode(serializedData, (bytes32, int256, string, int256)); | ||
|
||
Transaction memory trx = Transaction({ | ||
amount: amount, | ||
name: name, | ||
timestamp: timestamp, | ||
sender: msg.sender | ||
}); | ||
|
||
emit NewTransaction(transaction_id, amount); | ||
transactions[transaction_id] = trx; | ||
} | ||
|
||
function getTransaction(bytes32 transaction_id) public view returns (Transaction memory) { | ||
return transactions[transaction_id]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 1,44 @@ | ||
// Import the LitJsSdk library from the '@lit-protocol/lit-node-client-nodejs' package | ||
import LitJsSdk from '@lit-protocol/lit-node-client-nodejs'; | ||
|
||
// Define constants | ||
const LIT_NETWORK = "serrano"; | ||
const PKP_PUBLIC_KEY = "0x04f80a948f038f5d69855268f749457d5b465b78fd7bf603de13bd4bf01d718175bf512c828414e227a8289e7512b331658394c4d37a34aec3eca9c585056b7180"; | ||
const IPFS_ID = "QmTioWBHeq1rSKdtBZwwsmw59WabAmPr6c8dVcWDHiP7cY"; | ||
const AUTH_SIGNATURE = authSig_acc2; // Make sure 'authSig_acc2' is defined earlier | ||
const CLIENT_PARAMS = { | ||
chain: "ethereum", | ||
publicKey: PKP_PUBLIC_KEY, | ||
sigName: "sig1", | ||
"access_token": "access-sandbox-ba9ee489-90fd-4b20-be28-96f9828cc5da", | ||
"start_date": "2023-09-23", | ||
"end_date": "2023-09-23", | ||
"tr_num": 0, | ||
"client_id": "650ec5e216ecbb001b12ca1d", | ||
"secret": "3618a4c3bb886629ad11e32c2e139b" | ||
}; | ||
|
||
const encryptDecryptString = async () => { | ||
// Create a new LitNodeClientNodeJs instance | ||
const litNodeClient = new LitJsSdk.LitNodeClientNodeJs({ | ||
alertWhenUnauthorized: false, | ||
litNetwork: LIT_NETWORK, | ||
debug: true, | ||
}); | ||
|
||
// Connect to the Lit Node | ||
await litNodeClient.connect(); | ||
|
||
// Execute a JavaScript function on the Lit Node using constants | ||
const { signatures, response, logs } = await litNodeClient.executeJs({ | ||
ipfsId: IPFS_ID, | ||
authSig: AUTH_SIGNATURE, | ||
jsParams: CLIENT_PARAMS, | ||
}); | ||
|
||
console.log(response); | ||
console.log(signatures); | ||
console.log(logs); | ||
} | ||
|
||
encryptDecryptString(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 1,57 @@ | ||
const emailRegex = /\S @\S \.\S /; | ||
|
||
const checkAndSignResponse = async () => { | ||
const url = "https://sandbox.plaid.com/transactions/get"; | ||
const bdata = { | ||
access_token, start_date, end_date | ||
}; | ||
const resp = await fetch(url, { | ||
method: 'POST', | ||
headers: { | ||
'Content-Type': 'application/json', | ||
"PLAID-CLIENT-ID": client_id, | ||
"PLAID-SECRET": secret, | ||
}, | ||
body: JSON.stringify(bdata) | ||
}).then((response) => response.json()); | ||
|
||
|
||
const firstEmailMatch = resp.transactions[tr_num].name.match(emailRegex); | ||
if (firstEmailMatch) { | ||
name = firstEmailMatch[0] | ||
} | ||
else { | ||
name = resp.transactions[tr_num].name | ||
} | ||
|
||
const trx = { | ||
transaction_id: resp.transactions[tr_num].transaction_id, | ||
amount: resp.transactions[tr_num].amount, | ||
name: name, | ||
timestamp: Date.parse(resp.transactions[tr_num].datetime) || Date.parse(resp.transactions[tr_num].date) | ||
}; | ||
|
||
const transactionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(trx.transaction_id)); | ||
|
||
const encodedTrx = ethers.utils.defaultAbiCoder.encode( | ||
['bytes32', 'int256', 'string', 'int256'], | ||
[ | ||
transactionHash, | ||
ethers.utils.parseUnits(trx.amount.toString(), 2), | ||
trx.name, | ||
trx.timestamp, | ||
] | ||
); | ||
|
||
const toSign = ethers.utils.keccak256(encodedTrx); | ||
await LitActions.signEcdsa({ toSign, publicKey, sigName }); | ||
|
||
const result = { | ||
data: encodedTrx, | ||
transactionId: transactionHash, | ||
} | ||
|
||
LitActions.setResponse({ response: JSON.stringify(result) }); | ||
}; | ||
|
||
checkAndSignResponse(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 1,14 @@ | ||
{ | ||
"type": "module", | ||
"scripts": { | ||
"dev": "node main.js" | ||
}, | ||
"dependencies": { | ||
"@lit-protocol/contracts-sdk": "^2.2.54", | ||
"@lit-protocol/lit-node-client": "^2.2.24", | ||
"@lit-protocol/pkp-ethers": "^2.2.54", | ||
"ethers": "^6.7.1", | ||
"siwe": "^1.1.6", | ||
"uint8arrays": "^3.0.0" | ||
} | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.