What is included · How it fits together · Try it · What is tested · Known issues
This repository explores how tools that banks and broker-dealers run today could work as automated contracts on XDC: tokenized assets, signed agreements, forwards, futures, batches of assets, portfolios and a marketplace. A broker could run these with almost no infrastructure, and users could keep custody of their portfolios and move between brokers.
Warning
These are concept templates. They have not been audited and have not run on a real network. Don't use them with real assets. See Known issues.
| Contract | Purpose |
|---|---|
Asset.sol |
Represents commodities, securities or currencies as tokens, run by a broker |
Contract.sol |
Agreement tied to a document hash, signed by two parties and two witnesses, with counter-proposals |
DocToken.sol |
Registers documents as tokens |
Forward.sol |
Forward contract: the buyer pays now and collects the asset after a set date |
Future.sol |
Futures contract: a deposit and fee now, the price later, and a default if nobody pays in time |
MultiAssetBatch.sol |
Holds assets from several token contracts behind one set of keys |
marketplace.sol |
Listings, offers, auctions and escrow, forked from TronzitVeca's ERC1155-Marketplace, whose original repository is no longer online |
portfolio.sol |
User portfolio that holds tokens and trades through marketplaces, plus a ledger of accounts |
DelegatedExecutor.sol |
Holds funds and sends them on a user's behalf, controlled by keys |
GeneralDeployer.sol |
Deploys contracts from bytecode and keeps a list |
Macro_Contracts.sol |
Keeps a versioned history of a data schema. Acting on it is left to other contracts |
Micro_Contracts.sol |
Holds rate parameters for other contracts to read |
pyscripts/ has Python helpers for working with these contracts, and Documents/ has notes on individual contracts. The full article is in docs/WRITEUP.md.
flowchart LR
B[Broker] -->|tokenizes| A[Asset]
B -->|creates for a user| P[Portfolio]
P -->|lists, buys, bids| M[Marketplace]
A --> M
M <-->|liquidity across brokers| M2[Another broker's marketplace]
git clone https://github.com/CoinClubQuincy/XDC-Defi-Instruments.git
cd XDC-Defi-Instruments
git clone --depth 1 -b v4.9.6 https://github.com/OpenZeppelin/openzeppelin-contracts lib/openzeppelin-contracts
git clone --depth 1 -b v1.9.7 https://github.com/foundry-rs/forge-std lib/forge-std
forge testYou need Foundry.
To try it on a network, use the Apothem testnet:
| Network | Chain ID | RPC |
|---|---|---|
| Apothem testnet | 51 | https://rpc.apothem.network |
| XDC mainnet | 50 | https://rpc.xinfin.network |
forge create contracts/Defi-Tools/Asset.sol:Asset --rpc-url https://rpc.apothem.network --interactive --broadcast --legacy \
--constructor-args "https://example.com/{id}.json"Listed tokens stay with the seller until a sale, and auctioned tokens are held by the marketplace until the auction closes. Either way, the seller approves the marketplace on the token contract first. Sale money, replaced bids and fee shares wait in escrowAmount, and each person collects their own with withdrawEscrow. One recipient who can't receive money can't block a sale or an auction. Money behind an open offer is locked until the offer is accepted or cancelled.
test/foundry has 152 tests, including 3 fuzz tests, and runs in CI on every push.
| File | Tests | Covers |
|---|---|---|
Instruments.t.sol |
41 | Contract, Asset, Document, DelegateExecutor, MACRO, MICRO and ManagerDeployer |
Derivatives.t.sol |
35 | Forward, Future and MultiAssetBatch. Fuzz: money and tokens in equal money and tokens out |
Marketplace.t.sol |
53 | Marketplace, Portfolio and portfolioLedger. Fuzz: after each random action, the marketplace holds exactly what it owes |
Not tested: a real network, testnet or mainnet, an independent security audit, the Python helpers in pyscripts/, and whether the instruments meet any financial or securities rule.
Contracts already deployed are unaffected and keep the old behaviour. Most of these contracts could not work before, or lost money when they did.
Marketplace
acceptOfferpaid the buyer instead of the seller. It now pays the seller.claimAuctioncould be repeated, paying out of other people's money. An auction now closes once, and the creator can close it too.- Auctioned tokens are now held by the marketplace from the start, so a seller can't back out. The creator is paid when the auction closes, even if the winner's address refuses the tokens. The tokens then wait for
collectAuctionTokens, which can send them to another address. This closes a way to win an auction, refuse the tokens and get the bid back. - A sale or auction now keeps the fee in force when it was created, and the fee limit is now 20% instead of 100%.
- Auctions can last at most a year and start at most a year away. A very long duration could lock a bid for good.
- A transfer of zero keys could point a portfolio's
currentOwnerat the wrong address, which broke broker recovery. Anyone could do this. buyForwardsToken(token, id, date, price),initiateContract(date, price)andsign_proposal(documentHash)now name what the caller read. A handler or party can no longer change the terms while a purchase or signature is pending.- A
Forwardcan no longer be bought after its maturity date. MultiAssetBatchhas a newredeemAsset, so one stuck token contract can't trap the rest.- Sales could be bought more than once and after their deadline. Sales now end when sold, and overpayment is refunded.
- Sale money, replaced bids and fee shares now go to
escrowAmountinstead of being sent straight away, so sellers and recipients callwithdrawEscrow. - Fee money with no recipient used to be stuck. It now goes to
feeReserve, collected withwithdrawFeeReserve. Fee settings have limits. - Bids on auctions that don't exist used to take the money. They are now refused. If the tokens can't reach an auction winner, the winner's bid is refunded.
- Offers lock their money through the new
lockedEscrow.withdrawEscrowcan now take the full balance. New viewviewAuction. - An auction's
highestBidstarts at 0, and an increment can be bigger than the start price.
Portfolio
depositEscrowwithdrew instead of depositing.makeOfferandplaceBidsent no money. A portfolio couldn't hold tokens or spend what it received. New functionswithdrawFundsandapproveMarketplace.portfolioLedgerleft the marketplace keys in a contract that couldn't use them. They now go to whoever deploys the ledger.checkAccountno longer creates accounts while looking them up, andforwardFundsworks.deleteMarketplaceonly accepted marketplaces that were not saved. Saved marketplaces now keep their own names and numbers.resetOwnerkeeps working after the key moves.
Instruments
Contract: signing works, with each role signing once. The fourth signature completes it, and a counter-proposal clears signatures. The constructor takes a_holder, which also makesContractDeployerwork.DelegateExecutor:delegateCalllet anyone run outside code as this contract, so it is gone, replaced by a key-onlyexecuteCall. The password is gone too, because anything sent to a public chain is public.SendXRC1155works and token transfers are checked.Forward: a pending purchase fails if the terms change, and a forward redeems once and can't be transferred. NewcancelListingreturns an unsold asset.Future: the constructor always reverted, the handler and future tokens shared an ID, and the handler could take the deposit at any time. It is rewritten around those rules, with newcancelSale,inDefault,handlerBalance,listedanddivisable. The constructor takes one token address, andAssetTokensandTokenListare gone.MultiAssetBatch: adding and redeeming always reverted. Both work now, the limit is exact, and redeeming empties the batch.Document: the first document's token was also a handler key. They now have different IDs.Asset:moveAssetneeded an approval it could never have. It no longer does.Macro_Contracts.solandMicro_Contracts.solnow compile and work, with key-holder control.ForwardandFutureno longer define their ownASSETcontract, which clashed in Hardhat. Contracts refuse money and tokens they could never return.
Tooling
- Removed the Hardhat setup (
hardhat.config.js,package.json,package-lock.json,scripts/) and its tests, which could not run and no longer match, along with theLocktemplate. Foundry is the supported path.
The notes in Documents/ and the article in docs/WRITEUP.md describe the original design, and some details have changed.
| Issue | Effect |
|---|---|
| Brokers have real power | An Asset handler can move or destroy anyone's asset. A marketplace key holder can reset a portfolio's owner. That is the design, so a broker must be trusted |
| Any one key is enough | Any MultiAssetBatch or DelegateExecutor key can take everything the contract holds, and executeCall can do anything |
| Listed sales rely on approvals | Listed tokens stay with the seller. If the seller moves them, a buyer's purchase fails and the buyer keeps their money. Listings are not guaranteed to be fillable |
| A broker can raise fees for new listings | The fee is fixed when a listing is created, but a key holder can raise it up to 20% just before one is created. Check listFee after listing |
| A key holder can change a portfolio's marketplace | changeMarketplace also changes which brokers can recover it. Someone who steals the key can lock the original broker out |
One person can hold several Contract roles |
Roles are tokens, so one person with two addresses can sign as both a party and a witness |
Future terms include a default window |
redemptionTimeLimit can be as short as 1 second. Read it before buying, because missing it loses the deposit |
Forward and Future have one buyer |
Each contract handles one sale. Deploy a new one for each deal |
| Buyers must check the token | Forward and Future accept any token contract the handler names. A fake token passes every check |
| Listing loops grow | viewAllAssetsAndBids reads every sale. It is meant for off-chain reads |
| Licenses are mixed | Files declare GPL-3.0, MIT or UNLICENSED in their headers. Check each file before reuse |
More XDC and EVM projects
| Group | Project | What it is |
|---|---|---|
| Standards | XRC-SHARES | Dividend-paying NFT shares and SPLITS for a DApp |
| Standards | XRCDividendPlus | Dividend-paying XRC-20 token |
| Treasury and finance | Multi-Sig-Treasury | Treasury controlled by tokenized keys |
| Treasury and finance | XRC-Airdrop | Self-run token airdrop |
| Treasury and finance | XRC-Credit | Single-loan credit contract |
| Treasury and finance | XRC1155-Equities-Market | Shares, portfolios and a marketplace on ERC-1155 |
| Treasury and finance | NFTOTC | Over-the-counter sale of a token collection |
| Treasury and finance | DAppProxySwap | Currency swaps for a DApp through a fixed router |
| Identity and access | LoginMFAContract | Login with an NFT and a signed second factor |
| Identity and access | PrivateAccessToken | Token access to encrypted off-chain data |
| Identity and access | Delegated_Authority | KYC notary proof of concept |
| Identity and access | EncryptedNFT-Documents | NFTs tied to encrypted off-chain documents |
| Learning and libraries | XDC-LemonadeStand | Workshop DApp built on XRC-SHARES |
| Learning and libraries | Non-Financial-SmartContracts | Games, inventory, keys and on-chain repositories |
| Learning and libraries | ContractLedger | On-chain list of contract addresses |
| Learning and libraries | TimestampConverter | Timestamps to calendar dates in Solidity |
| Tools | XDC_Python_SDK_V1 | Python client for the XDC JSON-RPC API |
| Tools | PrivateXDCNode_SetupScript | Private XDC network setup scripts |
| Tools | WebApp_to_XDCPay | Working web app connected to XDCPay |
| Multi-chain | CoinClub_Framework | Python library with an XDC adapter |
| Interfaces | Contract_Interface_Model_Language | Interface documents for smart contracts and APIs |
Mixed. See the SPDX header of each file.





